Rework error messages to be shorter and to handle more special bash-isms

Example: we can point $* to argv

Fixes #1288
This commit is contained in:
ridiculousfish 2015-04-29 16:53:02 -07:00
parent 3d9c24196d
commit c91f70523c
8 changed files with 272 additions and 217 deletions

View file

@ -868,89 +868,6 @@ static bool expand_pid(const wcstring &instr_with_sep, expand_flags_t flags, std
return true;
}
void expand_variable_error(parser_t &parser, const wcstring &token, size_t token_pos, int error_pos, parse_error_list_t *errors)
{
size_t stop_pos = token_pos+1;
switch (token[stop_pos])
{
case BRACKET_BEGIN:
{
wchar_t *cpy = wcsdup(token.c_str());
*(cpy+token_pos)=0;
wchar_t *name = &cpy[stop_pos+1];
wchar_t *end = wcschr(name, BRACKET_END);
wchar_t *post = NULL;
int is_var=0;
if (end)
{
post = end+1;
*end = 0;
if (!wcsvarname(name))
{
is_var = 1;
}
}
if (is_var)
{
append_syntax_error(errors,
error_pos,
COMPLETE_VAR_BRACKET_DESC,
cpy,
name,
post);
}
else
{
append_syntax_error(errors,
error_pos,
COMPLETE_VAR_BRACKET_DESC,
L"",
L"VARIABLE",
L"");
}
free(cpy);
break;
}
case INTERNAL_SEPARATOR:
{
append_syntax_error(errors,
error_pos,
COMPLETE_VAR_PARAN_DESC);
break;
}
case 0:
{
append_syntax_error(errors,
error_pos,
COMPLETE_VAR_NULL_DESC);
break;
}
default:
{
wchar_t token_stop_char = token[stop_pos];
// Unescape (see http://github.com/fish-shell/fish-shell/issues/50)
if (token_stop_char == ANY_CHAR)
token_stop_char = L'?';
else if (token_stop_char == ANY_STRING || token_stop_char == ANY_STRING_RECURSIVE)
token_stop_char = L'*';
append_syntax_error(errors,
error_pos,
(token_stop_char == L'?' ? COMPLETE_YOU_WANT_STATUS : COMPLETE_VAR_DESC),
token_stop_char);
break;
}
}
}
/**
Parse an array slicing specification
Returns 0 on success.
@ -1115,7 +1032,10 @@ static int expand_variables(parser_t &parser, const wcstring &instr, std::vector
if (var_len == 0)
{
expand_variable_error(parser, instr, stop_pos-1, -1, errors);
if (errors)
{
parse_util_expand_variable_error(instr, 0 /* global_token_pos */, c, errors);
}
is_ok = false;
break;

View file

@ -197,18 +197,6 @@ wcstring replace_home_directory_with_tilde(const wcstring &str);
*/
int expand_is_clean(const wchar_t *in);
/**
Perform error reporting for a syntax error related to the variable
expansion beginning at the specified character of the specified
token. This function will call the error function with an
explanatory string about what is wrong with the specified token.
\param token The token containing the error
\param token_pos The position where the expansion begins
\param error_pos The position on the line to report to the error function.
*/
void expand_variable_error(parser_t &parser, const wcstring &token, size_t token_pos, int error_pos);
/**
Testing function for getting all process names.
*/

View file

@ -184,6 +184,8 @@ static wcstring comma_join(const wcstring_list_t &lst)
#define do_test(e) do { if (! (e)) err(L"Test failed on line %lu: %s", __LINE__, #e); } while (0)
#define do_test1(e, msg) do { if (! (e)) err(L"Test failed on line %lu: %ls", __LINE__, (msg)); } while (0)
/* Test sane escapes */
static void test_unescape_sane()
{
@ -2009,7 +2011,7 @@ static void test_complete(void)
do_test(completions.at(0).completion == L"r");
/* Add a function and test completing it in various ways */
struct function_data_t func_data;
struct function_data_t func_data = {};
func_data.name = L"scuttlebutt";
func_data.definition = L"echo gongoozle";
function_add(func_data, parser_t::principal_parser());
@ -3593,7 +3595,23 @@ static void test_error_messages()
}
error_tests[] =
{
{L"echo $?", COMPLETE_YOU_WANT_STATUS}
{L"echo $^", ERROR_BAD_VAR_CHAR1},
{L"echo foo${a}bar", ERROR_BRACKETED_VARIABLE1},
{L"echo foo\"${a}\"bar", ERROR_BRACKETED_VARIABLE_QUOTED1},
{L"echo foo\"${\"bar", ERROR_BAD_VAR_CHAR1},
{L"echo $?", ERROR_NOT_STATUS},
{L"echo $$", ERROR_NOT_PID},
{L"echo $#", ERROR_NOT_ARGV_COUNT},
{L"echo $@", ERROR_NOT_ARGV_AT},
{L"echo $*", ERROR_NOT_ARGV_STAR},
{L"echo $", ERROR_NO_VAR_NAME},
{L"echo foo\"$\"bar", ERROR_NO_VAR_NAME},
{L"echo \"foo\"$\"bar\"", ERROR_NO_VAR_NAME},
{L"echo foo $ bar", ERROR_NO_VAR_NAME},
{L"echo foo$(foo)bar", ERROR_BAD_VAR_SUBCOMMAND1},
{L"echo \"foo$(foo)bar\"", ERROR_BAD_VAR_SUBCOMMAND1},
{L"echo foo || echo bar", ERROR_BAD_OR},
{L"echo foo && echo bar", ERROR_BAD_AND}
};
parse_error_list_t errors;
@ -3605,7 +3623,7 @@ static void test_error_messages()
do_test(! errors.empty());
if (! errors.empty())
{
do_test(string_matches_format(errors.at(0).text, test->error_text_format));
do_test1(string_matches_format(errors.at(0).text, test->error_text_format), test->src);
}
}
}

View file

@ -184,16 +184,9 @@ void parse_error_offset_source_start(parse_error_list_t *errors, size_t amt);
/** Error message on a function that calls itself immediately */
#define INFINITE_FUNC_RECURSION_ERR_MSG _( L"The function '%ls' calls itself immediately, which would result in an infinite loop.")
/** Error message on reaching maximum call stack depth */
#define CALL_STACK_LIMIT_EXCEEDED_ERR_MSG _( L"The function call stack limit has been exceeded. Do you have an accidental infinite loop?")
/** Error message when a non-string token is found when expecting a command name */
#define CMD_OR_ERR_MSG _( L"Expected a command, but instead found a pipe. 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, but instead found an '&'. 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'")
@ -213,41 +206,60 @@ void parse_error_offset_source_start(parse_error_list_t *errors, size_t amt);
#define WILDCARD_ERR_MSG _( L"No matches for wildcard '%ls'.")
/** Error when using break outside of loop */
#define INVALID_BREAK_ERR_MSG _( L"break command while not inside of loop" )
#define INVALID_BREAK_ERR_MSG _( L"'break' while not inside of loop" )
/** Error when using continue outside of loop */
#define INVALID_CONTINUE_ERR_MSG _( L"continue command while not inside of loop" )
#define INVALID_CONTINUE_ERR_MSG _( L"'continue' 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" )
#define INVALID_RETURN_ERR_MSG _( L"'return' outside of function definition" )
/*** Error messages. The number is a reminder of how many format specifiers are contained. */
/** Error for (e.g.) $^ */
#define ERROR_BAD_VAR_CHAR1 _( L"$%lc is not a valid variable in fish." )
/** Error for ${a} */
#define ERROR_BRACKETED_VARIABLE1 _( L"Variables cannot be bracketed. In fish, please use {$%ls}." )
/** Error for "${a}" */
#define ERROR_BRACKETED_VARIABLE_QUOTED1 _( L"Variables cannot be bracketed. In fish, please use \"$%ls\"." )
/** Error issued on $? */
#define ERROR_NOT_STATUS _( L"$? is not the exit status. In fish, please use $status.")
/** Error issued on $$ */
#define ERROR_NOT_PID _( L"$$ is not the pid. In fish, please use %%self.")
/** Error issued on $# */
#define ERROR_NOT_ARGV_COUNT _( L"$# is not supported. In fish, please use 'count $argv'.")
/** Error issued on $@ */
#define ERROR_NOT_ARGV_AT _( L"$@ is not supported. In fish, please use $argv.")
/** Error issued on $(...) */
#define ERROR_BAD_VAR_SUBCOMMAND1 _( L"$(...) is not supported. In fish, please use '(%ls)'." )
/** Error issued on $* */
#define ERROR_NOT_ARGV_STAR _( L"$* is not supported. In fish, please use $argv." )
/** Error issued on $ */
#define ERROR_NO_VAR_NAME _( L"Expected a variable name after this $.")
/** Error on || */
#define ERROR_BAD_OR _( L"Unsupported use of '||'. In fish, please use 'COMMAND; or COMMAND'.")
/** Error on && */
#define ERROR_BAD_AND _( L"Unsupported use of '&&'. In fish, please use 'COMMAND; and COMMAND'.")
/** Error on foo=bar */
#define ERROR_BAD_EQUALS_IN_COMMAND5 _( L"Unsupported use of '='. To run '%ls' with a modified environment, please use 'env %ls=%ls %ls%ls'")
/** 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'.")
#define ERROR_BAD_COMMAND_ASSIGN_ERR_MSG _( L"Unsupported use of '='. In fish, please use 'set %ls %ls'.")
/**
Error issued on invalid variable name
*/
#define COMPLETE_VAR_DESC _( L"The '$' character begins a variable name. The character '%lc', which directly followed a '$', is not allowed as a part of a variable name, and variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'.")
/**
Error issued on $?
*/
#define COMPLETE_YOU_WANT_STATUS _( L"$? is not a valid variable in fish. If you want the exit status of the last command, try $status.")
/**
Error issued on invalid variable name
*/
#define COMPLETE_VAR_NULL_DESC _( L"The '$' begins a variable name. It was given at the end of an argument. Variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'.")
/**
Error issued on invalid variable name
*/
#define COMPLETE_VAR_BRACKET_DESC _( L"Did you mean %ls{$%ls}%ls? The '$' character begins a variable name. A bracket, which directly followed a '$', is not allowed as a part of a variable name, and variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'." )
/**
Error issued on invalid variable name
*/
#define COMPLETE_VAR_PARAN_DESC _( L"Did you mean (COMMAND)? In fish, the '$' character is only used for accessing variables. To learn more about command substitution in fish, type 'help expand-command-substitution'.")
/**
While block description

View file

@ -796,8 +796,7 @@ parse_execution_result_t parse_execution_context_t::handle_command_not_found(con
/* Looks like a command */
this->report_error(statement_node,
_(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,
ERROR_BAD_EQUALS_IN_COMMAND5,
argument.c_str(),
name_str.c_str(),
val_str.c_str(),
@ -807,39 +806,21 @@ parse_execution_result_t parse_execution_context_t::handle_command_not_found(con
else
{
this->report_error(statement_node,
COMMAND_ASSIGN_ERR_MSG,
cmd,
ERROR_BAD_COMMAND_ASSIGN_ERR_MSG,
name_str.c_str(),
val_str.c_str());
}
}
else if (cmd[0]==L'$' || cmd[0] == VARIABLE_EXPAND || cmd[0] == VARIABLE_EXPAND_SINGLE)
else if ((cmd[0] == L'$' || cmd[0] == VARIABLE_EXPAND || cmd[0] == VARIABLE_EXPAND_SINGLE) && cmd[1] != L'\0')
{
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)
{
this->report_error(statement_node,
_(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
{
this->report_error(statement_node,
_(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);
}
this->report_error(statement_node,
_(L"Variables may not be used as commands. In fish, please define a function or use 'eval %ls'."),
cmd+1);
}
else if (wcschr(cmd, L'$'))
{
this->report_error(statement_node,
_(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,
_(L"Commands may not contain variables. In fish, please use 'eval %ls'."),
cmd);
}
else if (err_code!=ENOENT)

View file

@ -880,7 +880,7 @@ void parse_ll_t::parse_error_failed_production(struct parse_stack_element_t &sta
if (prev_pipe != NULL)
{
/* The pipe of the previous job abuts our current token. So we have ||. */
this->parse_error(token, parse_error_double_pipe, CMD_OR_ERR_MSG);
this->parse_error(token, parse_error_double_pipe, ERROR_BAD_OR);
done = true;
}
}
@ -893,7 +893,7 @@ void parse_ll_t::parse_error_failed_production(struct parse_stack_element_t &sta
if (prev_background != NULL)
{
/* We have &&. */
this->parse_error(token, parse_error_double_background, CMD_AND_ERR_MSG);
this->parse_error(token, parse_error_double_background, ERROR_BAD_AND);
done = true;
}
}

View file

@ -1019,6 +1019,23 @@ static bool append_syntax_error(parse_error_list_t *errors, const parse_node_t &
return true;
}
static bool append_syntax_error(parse_error_list_t *errors, size_t source_location, const wchar_t *fmt, ...)
{
parse_error_t error;
error.source_start = source_location;
error.source_length = 0;
error.code = parse_error_syntax;
va_list va;
va_start(va, fmt);
error.text = vformat_string(fmt, va);
va_end(va);
errors->push_back(error);
return true;
}
/**
Returns 1 if the specified command is a builtin that may not be used in a pipeline
*/
@ -1059,88 +1076,191 @@ static bool first_argument_is_help(const parse_node_tree_t &node_tree, const par
return is_help;
}
void parse_util_expand_variable_error(const parse_node_t &node, const wcstring &token, size_t token_pos, size_t error_pos, parse_error_list_t *out_errors)
/* If a var name or command is too long for error reporting, make it shorter */
static wcstring truncate_string(const wcstring &str)
{
size_t stop_pos = token_pos+1;
const size_t max_len = 16;
wcstring result(str, 0, max_len);
if (str.size() > max_len)
{
// Truncate!
if (ellipsis_char == L'\x2026')
{
result.at(max_len - 1) = ellipsis_char;
}
else
{
result.replace(max_len - 3, 3, L"...");
}
}
return result;
}
switch (token[stop_pos])
/* Given a wide character immediately after a dollar sign, return the appropriate error message. For example, if wc is @, then the variable name was $@ and we suggest $argv. */
static const wchar_t *error_format_for_character(wchar_t wc)
{
switch (wc)
{
case L'?': return ERROR_NOT_STATUS;
case L'#': return ERROR_NOT_ARGV_COUNT;
case L'@': return ERROR_NOT_ARGV_AT;
case L'*': return ERROR_NOT_ARGV_STAR;
case L'$':
case VARIABLE_EXPAND:
case VARIABLE_EXPAND_SINGLE:
case VARIABLE_EXPAND_EMPTY:
return ERROR_NOT_PID;
default: return ERROR_BAD_VAR_CHAR1;
}
}
void parse_util_expand_variable_error(const wcstring &token, size_t global_token_pos, size_t dollar_pos, parse_error_list_t *errors)
{
// Note that dollar_pos is probably VARIABLE_EXPAND or VARIABLE_EXPAND_SINGLE, not a literal dollar sign
assert(errors != NULL);
assert(dollar_pos < token.size());
const bool double_quotes = (token.at(dollar_pos) == VARIABLE_EXPAND_SINGLE);
const size_t start_error_count = errors->size();
const size_t global_dollar_pos = global_token_pos + dollar_pos;
const size_t global_after_dollar_pos = global_dollar_pos + 1;
wchar_t char_after_dollar = (dollar_pos + 1 >= token.size() ? L'\0' : token.at(dollar_pos + 1));
switch (char_after_dollar)
{
case BRACKET_BEGIN:
case L'{':
{
wchar_t *cpy = wcsdup(token.c_str());
*(cpy+token_pos)=0;
wchar_t *name = &cpy[stop_pos+1];
wchar_t *end = wcschr(name, BRACKET_END);
wchar_t *post = NULL;
int is_var=0;
if (end)
// The BRACKET_BEGIN is for unquoted, the { is for quoted. Anyways we have (possible quoted) ${. See if we have a }, and the stuff in between is variable material. If so, report a bracket error. Otherwise just complain about the ${.
bool looks_like_variable = false;
size_t closing_bracket = token.find(char_after_dollar == L'{' ? L'}' : BRACKET_END, dollar_pos + 2);
wcstring var_name;
if (closing_bracket != wcstring::npos)
{
post = end+1;
*end = 0;
if (!wcsvarname(name))
{
is_var = 1;
}
size_t var_start = dollar_pos + 2, var_end = closing_bracket;
var_name = wcstring(token, var_start, var_end - var_start);
looks_like_variable = ! var_name.empty() && wcsvarname(var_name.c_str()) == NULL;
}
if (is_var)
if (looks_like_variable)
{
append_syntax_error(out_errors,
node,
COMPLETE_VAR_BRACKET_DESC,
cpy,
name,
post);
append_syntax_error(errors,
global_after_dollar_pos,
double_quotes ? ERROR_BRACKETED_VARIABLE_QUOTED1 : ERROR_BRACKETED_VARIABLE1,
truncate_string(var_name).c_str());
}
else
{
append_syntax_error(out_errors,
node,
COMPLETE_VAR_BRACKET_DESC,
L"",
L"VARIABLE",
L"");
append_syntax_error(errors,
global_after_dollar_pos,
ERROR_BAD_VAR_CHAR1,
L'{');
}
free(cpy);
break;
}
case INTERNAL_SEPARATOR:
{
append_syntax_error(out_errors,
node,
COMPLETE_VAR_PARAN_DESC);
//e.g.: echo foo"$"baz
// These are only ever quotes, not command substitutions. Command substitutions are handled earlier.
append_syntax_error(errors,
global_dollar_pos,
ERROR_NO_VAR_NAME);
break;
}
case 0:
case '(':
{
append_syntax_error(out_errors,
node,
COMPLETE_VAR_NULL_DESC);
// e.g.: 'echo "foo$(bar)baz"
// Try to determine what's in the parens.
wcstring token_after_parens;
wcstring paren_text;
size_t open_parens = dollar_pos + 1, cmdsub_start = 0, cmdsub_end = 0;
if (parse_util_locate_cmdsubst_range(token,
&open_parens,
&paren_text,
&cmdsub_start,
&cmdsub_end,
true) > 0)
{
token_after_parens = tok_first(paren_text.c_str());
}
/* Make sure we always show something */
if (token_after_parens.empty())
{
token_after_parens = L"...";
}
append_syntax_error(errors,
global_dollar_pos,
ERROR_BAD_VAR_SUBCOMMAND1,
truncate_string(token_after_parens).c_str());
break;
}
case L'\0':
{
append_syntax_error(errors,
global_dollar_pos,
ERROR_NO_VAR_NAME);
break;
}
default:
{
wchar_t token_stop_char = token[stop_pos];
// Unescape (see http://github.com/fish-shell/fish-shell/issues/50)
wchar_t token_stop_char = char_after_dollar;
// Unescape (see #50)
if (token_stop_char == ANY_CHAR)
token_stop_char = L'?';
else if (token_stop_char == ANY_STRING || token_stop_char == ANY_STRING_RECURSIVE)
token_stop_char = L'*';
append_syntax_error(out_errors,
node,
(token_stop_char == L'?' ? COMPLETE_YOU_WANT_STATUS : COMPLETE_VAR_DESC),
/* Determine which error message to use. The format string may not consume all the arguments we pass but that's harmless. */
const wchar_t *error_fmt = error_format_for_character(token_stop_char);
append_syntax_error(errors,
global_after_dollar_pos,
error_fmt,
token_stop_char);
break;
}
}
// We should have appended exactly one error
assert(errors->size() == start_error_count + 1);
}
/* Detect cases like $(abc). Given an arg like foo(bar), let arg_src be foo and cmdsubst_src be bar. If arg ends with VARIABLE_EXPAND, then report an error. */
static parser_test_error_bits_t detect_dollar_cmdsub_errors(size_t arg_src_offset, const wcstring &arg_src, const wcstring &cmdsubst_src, parse_error_list_t *out_errors)
{
parser_test_error_bits_t result_bits = 0;
wcstring unescaped_arg_src;
if (unescape_string(arg_src, &unescaped_arg_src, UNESCAPE_SPECIAL))
{
if (! unescaped_arg_src.empty())
{
wchar_t last = unescaped_arg_src.at(unescaped_arg_src.size() - 1);
if (last == VARIABLE_EXPAND)
{
result_bits |= PARSER_TEST_ERROR;
if (out_errors != NULL)
{
wcstring subcommand_first_token = tok_first(cmdsubst_src.c_str());
if (subcommand_first_token.empty())
{
// e.g. $(). Report somthing.
subcommand_first_token = L"...";
}
append_syntax_error(out_errors,
arg_src_offset + arg_src.size() - 1, // global position of the dollar
ERROR_BAD_VAR_SUBCOMMAND1,
truncate_string(subcommand_first_token).c_str());
}
}
}
}
return result_bits;
}
/**
Test if this argument contains any errors. Detected errors include
@ -1198,10 +1318,19 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const parse_node_t
/* Our command substitution produced error offsets relative to its source. Tweak the offsets of the errors in the command substitution to account for both its offset within the string, and the offset of the node */
size_t error_offset = cmd_sub_start + 1 + node.source_start;
parse_error_offset_source_start(&subst_errors, error_offset);
if (out_errors != NULL)
{
out_errors->insert(out_errors->end(), subst_errors.begin(), subst_errors.end());
/* Hackish. Take this opportunity to report $(...) errors. We do this because after we've replaced with internal separators, we can't distinguish between "" and (), and also we no longer have the source of the command substitution. As an optimization, this is only necessary if the last character is a $. */
if (cmd_sub_start > 0 && working_copy.at(cmd_sub_start - 1) == L'$')
{
err |= detect_dollar_cmdsub_errors(node.source_start,
working_copy.substr(0, cmd_sub_start),
subst,
out_errors);
}
}
break;
}
@ -1230,14 +1359,18 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const parse_node_t
{
wchar_t next_char = (idx + 1 < unesc_size ? unesc.at(idx + 1) : L'\0');
if (next_char != VARIABLE_EXPAND &&
next_char != VARIABLE_EXPAND_SINGLE &&
! wcsvarchr(next_char))
if (next_char != VARIABLE_EXPAND && next_char != VARIABLE_EXPAND_SINGLE && ! wcsvarchr(next_char))
{
err=1;
if (out_errors)
{
parse_util_expand_variable_error(node, unesc, idx, node.source_start, out_errors);
/* We have something like $$$^.... Back up until we reach the first $ */
size_t first_dollar = idx;
while (first_dollar > 0 && (unesc.at(first_dollar-1) == VARIABLE_EXPAND || unesc.at(first_dollar-1) == VARIABLE_EXPAND_SINGLE))
{
first_dollar--;
}
parse_util_expand_variable_error(unesc, node.source_start, first_dollar, out_errors);
}
}

View file

@ -20,7 +20,7 @@
\param begin the starting paranthesis of the subshell
\param end the ending paranthesis of the subshell
\param accept_incomplete whether to permit missing closing parenthesis
\return -1 on syntax error, 0 if no subshells exist and 1 on sucess
\return -1 on syntax error, 0 if no subshells exist and 1 on success
*/
int parse_util_locate_cmdsubst(const wchar_t *in,
@ -43,7 +43,7 @@ int parse_util_locate_slice(const wchar_t *in,
\param out_start On output, the offset of the start of the command substitution (open paren)
\param out_end On output, the offset of the end of the command substitution (close paren), or the end of the string if it was incomplete
\param accept_incomplete whether to permit missing closing parenthesis
\return -1 on syntax error, 0 if no subshells exist and 1 on sucess
\return -1 on syntax error, 0 if no subshells exist and 1 on success
*/
int parse_util_locate_cmdsubst_range(const wcstring &str,
@ -188,4 +188,7 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, pars
*/
parser_test_error_bits_t parse_util_detect_errors_in_argument(const parse_node_t &node, const wcstring &arg_src, parse_error_list_t *out_errors = NULL);
/* Given a string containing a variable expansion error, append an appropriate error to the errors list. The global_token_pos is the offset of the token in the larger source, and the dollar_pos is the offset of the offending dollar sign within the token. */
void parse_util_expand_variable_error(const wcstring &token, size_t global_token_pos, size_t dollar_pos, parse_error_list_t *out_errors);
#endif