Merge of "ast" branch, providing fish with a unified parser, used for execution, syntax coloring, completions, abbreviations, etc. This also bestows fish with a formalized grammar, which is 'documented' in a comment in parse_tree.h.

The parser here is a LL(2) parser, which is handwritten (to avoid complicating the build process and to maintain good control over error reporting, thread safety, etc). Later it's worth exploring using parser generators (lemon, etc) or other tools to simplify things.

This commit enables the new parser for syntax highlighting, completions, and abbreviations. Syntax highlighting retains the old implementation (disabled), which will be removed shortly. There is also support for a new execution model, based on the new parser, but it is disabled by default (can be enabled by setting the fish_new_parser variable to 1).

There's also lots of new tests, and some machinery for selecting which tests to run.

After living on this commit for a while, we'll enable the new execution model by default, and then begin to tear down the machinery of the old one (the block types, builtin_end, the parser_t junk, etc.). After that we can pursue even more exotic execution models, like multithreaded ones.

(The branch name is really a misnomer - the tree here is a parse tree, or concrete syntax tree, not an abstract one.)

Fixes #557
This commit is contained in:
ridiculousfish 2014-01-13 13:57:35 -08:00
commit 290aae80e1
49 changed files with 7911 additions and 1722 deletions

View file

@ -92,7 +92,7 @@ FISH_OBJS := function.o builtin.o complete.o env.o exec.o expand.o \
env_universal.o env_universal_common.o input_common.o event.o \
signal.o io.o parse_util.o common.o screen.o path.o autoload.o \
parser_keywords.o iothread.o color.o postfork.o \
builtin_test.o
builtin_test.o parse_tree.o parse_productions.o parse_execution.cpp
FISH_INDENT_OBJS := fish_indent.o print_help.o common.o \
parser_keywords.o wutil.o tokenizer.o

View file

@ -195,7 +195,6 @@ autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcs
bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload, const wcstring_list_t &path_list)
{
/* Note that we are NOT locked in this function! */
size_t i;
bool reloaded = 0;
/* Try using a cached function. If we really want the function to be loaded, require that it be really loaded. If we're not reloading, allow stale functions. */
@ -276,7 +275,7 @@ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_
if (! has_script_source)
{
/* Iterate over path searching for suitable completion files */
for (i=0; i<path_list.size(); i++)
for (size_t i=0; i<path_list.size(); i++)
{
wcstring next = path_list.at(i);
wcstring path = next + L"/" + cmd + L".fish";

View file

@ -64,6 +64,7 @@
#include "expand.h"
#include "path.h"
#include "history.h"
#include "parse_tree.h"
/**
The default prompt for the read command
@ -164,7 +165,7 @@ static const io_chain_t *real_io;
/**
Counts the number of non null pointers in the specified array
*/
static int builtin_count_args(wchar_t **argv)
static int builtin_count_args(const wchar_t * const * argv)
{
int argc = 1;
while (argv[argc] != NULL)
@ -243,9 +244,6 @@ wcstring builtin_help_get(parser_t &parser, const wchar_t *name)
static void builtin_print_help(parser_t &parser, const wchar_t *cmd, wcstring &b)
{
int is_short = 0;
if (&b == &stderr_buffer)
{
stderr_buffer.append(parser.current_line());
@ -259,7 +257,7 @@ static void builtin_print_help(parser_t &parser, const wchar_t *cmd, wcstring &b
wchar_t *str = wcsdup(h.c_str());
if (str)
{
bool is_short = false;
if (&b == &stderr_buffer)
{
@ -278,7 +276,7 @@ static void builtin_print_help(parser_t &parser, const wchar_t *cmd, wcstring &b
int cut=0;
int i;
is_short = 1;
is_short = true;
/*
First move down 4 lines
@ -689,7 +687,7 @@ static int builtin_bind(parser_t &parser, wchar_t **argv)
default:
{
res = STATUS_BUILTIN_ERROR;
append_format(stderr_buffer, _(L"%ls: Expected zero or two parameters, got %d"), argv[0], argc-woptind);
append_format(stderr_buffer, _(L"%ls: Expected zero or two parameters, got %d\n"), argv[0], argc-woptind);
break;
}
}
@ -737,7 +735,6 @@ static int builtin_block(parser_t &parser, wchar_t **argv)
int scope=UNSET;
int erase = 0;
int argc=builtin_count_args(argv);
int type = (1<<EVENT_ANY);
woptind=0;
@ -835,7 +832,7 @@ static int builtin_block(parser_t &parser, wchar_t **argv)
block_t *block = parser.block_at_index(block_idx);
event_blockage_t eb = {};
eb.typemask = type;
eb.typemask = (1<<EVENT_ANY);
switch (scope)
{
@ -1041,20 +1038,22 @@ static int builtin_emit(parser_t &parser, wchar_t **argv)
static int builtin_generic(parser_t &parser, wchar_t **argv)
{
int argc=builtin_count_args(argv);
/* Hackish - if we have no arguments other than the command, we are a "naked invocation" and we just print help */
if (argc == 1)
{
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_ERROR;
}
woptind=0;
static const struct woption
long_options[] =
{
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
while (1)
{
@ -1751,12 +1750,325 @@ static int builtin_pwd(parser_t &parser, wchar_t **argv)
}
}
/* This is nearly identical to builtin_function, and is intended to be the successor (with no block manipulation, no function/end split) */
int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstring &contents, wcstring *out_err)
{
assert(out_err != NULL);
/* wgetopt expects 'function' as the first argument. Make a new wcstring_list with that property. */
wcstring_list_t args;
args.push_back(L"function");
args.insert(args.end(), c_args.begin(), c_args.end());
/* Hackish const_cast matches the one in builtin_run */
const null_terminated_array_t<wchar_t> argv_array(args);
wchar_t **argv = const_cast<wchar_t **>(argv_array.get());
int argc = builtin_count_args(argv);
int res=STATUS_BUILTIN_OK;
wchar_t *desc=0;
std::vector<event_t> events;
std::auto_ptr<wcstring_list_t> named_arguments(NULL);
wchar_t *name = 0;
bool shadows = true;
woptind=0;
const struct woption long_options[] =
{
{ L"description", required_argument, 0, 'd' },
{ L"on-signal", required_argument, 0, 's' },
{ L"on-job-exit", required_argument, 0, 'j' },
{ L"on-process-exit", required_argument, 0, 'p' },
{ L"on-variable", required_argument, 0, 'v' },
{ L"on-event", required_argument, 0, 'e' },
{ L"help", no_argument, 0, 'h' },
{ L"argument-names", no_argument, 0, 'a' },
{ L"no-scope-shadowing", no_argument, 0, 'S' },
{ 0, 0, 0, 0 }
};
while (1 && (!res))
{
int opt_index = 0;
int opt = wgetopt_long(argc,
argv,
L"d:s:j:p:v:e:haS",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
case 0:
if (long_options[opt_index].flag != 0)
break;
append_format(*out_err,
BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
res = 1;
break;
case 'd':
desc=woptarg;
break;
case 's':
{
int sig = wcs2sig(woptarg);
if (sig < 0)
{
append_format(*out_err,
_(L"%ls: Unknown signal '%ls'\n"),
argv[0],
woptarg);
res=1;
break;
}
events.push_back(event_t::signal_event(sig));
break;
}
case 'v':
{
if (wcsvarname(woptarg))
{
append_format(*out_err,
_(L"%ls: Invalid variable name '%ls'\n"),
argv[0],
woptarg);
res=STATUS_BUILTIN_ERROR;
break;
}
events.push_back(event_t::variable_event(woptarg));
break;
}
case 'e':
{
events.push_back(event_t::generic_event(woptarg));
break;
}
case 'j':
case 'p':
{
pid_t pid;
wchar_t *end;
event_t e(EVENT_ANY);
if ((opt == 'j') &&
(wcscasecmp(woptarg, L"caller") == 0))
{
int job_id = -1;
if (is_subshell)
{
size_t block_idx = 0;
/* Find the outermost substitution block */
for (block_idx = 0; ; block_idx++)
{
const block_t *b = parser.block_at_index(block_idx);
if (b == NULL || b->type() == SUBST)
break;
}
/* Go one step beyond that, to get to the caller */
const block_t *caller_block = parser.block_at_index(block_idx + 1);
if (caller_block != NULL && caller_block->job != NULL)
{
job_id = caller_block->job->job_id;
}
}
if (job_id == -1)
{
append_format(*out_err,
_(L"%ls: Cannot find calling job for event handler\n"),
argv[0]);
res=1;
}
else
{
e.type = EVENT_JOB_ID;
e.param1.job_id = job_id;
}
}
else
{
errno = 0;
pid = fish_wcstoi(woptarg, &end, 10);
if (errno || !end || *end)
{
append_format(*out_err,
_(L"%ls: Invalid process id %ls\n"),
argv[0],
woptarg);
res=1;
break;
}
e.type = EVENT_EXIT;
e.param1.pid = (opt=='j'?-1:1)*abs(pid);
}
if (res)
{
/* nothing */
}
else
{
events.push_back(e);
}
break;
}
case 'a':
if (named_arguments.get() == NULL)
named_arguments.reset(new wcstring_list_t);
break;
case 'S':
shadows = 0;
break;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_OK;
case '?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
res = 1;
break;
}
}
if (!res)
{
if (argc == woptind)
{
append_format(*out_err,
_(L"%ls: Expected function name\n"),
argv[0]);
res=1;
}
else if (wcsfuncname(argv[woptind]))
{
append_format(*out_err,
_(L"%ls: Illegal function name '%ls'\n"),
argv[0],
argv[woptind]);
res=1;
}
else if (parser_keywords_is_reserved(argv[woptind]))
{
append_format(*out_err,
_(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name\n"),
argv[0],
argv[woptind]);
res=1;
}
else if (! wcslen(argv[woptind]))
{
append_format(*out_err, _(L"%ls: No function name given\n"), argv[0]);
}
else
{
name = argv[woptind++];
if (named_arguments.get())
{
while (woptind < argc)
{
if (wcsvarname(argv[woptind]))
{
append_format(*out_err,
_(L"%ls: Invalid variable name '%ls'\n"),
argv[0],
argv[woptind]);
res = STATUS_BUILTIN_ERROR;
break;
}
named_arguments->push_back(argv[woptind++]);
}
}
else if (woptind != argc)
{
append_format(*out_err,
_(L"%ls: Expected one argument, got %d\n"),
argv[0],
argc);
res=1;
}
}
}
if (res)
{
builtin_print_help(parser, argv[0], *out_err);
}
else
{
function_data_t d;
d.name = name;
if (desc)
d.description = desc;
d.events.swap(events);
d.shadows = shadows;
if (named_arguments.get())
d.named_arguments.swap(*named_arguments);
for (size_t i=0; i<d.events.size(); i++)
{
event_t &e = d.events.at(i);
e.function_name = d.name;
}
d.definition = contents.c_str();
// TODO: fix def_offset inside function_add
function_add(d, parser);
}
return res;
}
/**
The function builtin, used for providing subroutines.
It calls various functions from function.c to perform any heavy lifting.
*/
static int builtin_function(parser_t &parser, wchar_t **argv)
{
/* Hack hack hack - with the new parser, this is only invoked for help */
if (parser_use_ast())
{
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_OK;
}
int argc = builtin_count_args(argv);
int res=STATUS_BUILTIN_OK;
wchar_t *desc=0;
@ -1982,6 +2294,10 @@ static int builtin_function(parser_t &parser, wchar_t **argv)
res=1;
}
else if (! wcslen(argv[woptind]))
{
append_format(stderr_buffer, _(L"%ls: No function name given\n"), argv[0]);
}
else
{
@ -3018,10 +3334,7 @@ static int builtin_source(parser_t &parser, wchar_t ** argv)
argc = builtin_count_args(argv);
const wchar_t *fn;
const wchar_t *fn_intern;
const wchar_t *fn, *fn_intern;
if (argc < 2 || (wcscmp(argv[1], L"-") == 0))
{
@ -3054,18 +3367,7 @@ static int builtin_source(parser_t &parser, wchar_t ** argv)
return STATUS_BUILTIN_ERROR;
}
fn = wrealpath(argv[1], NULL);
if (!fn)
{
fn_intern = intern(argv[1]);
}
else
{
fn_intern = intern(fn);
free((void *)fn);
}
fn_intern = intern(argv[1]);
}
parser.push_block(new source_block_t(fn_intern));
@ -3369,6 +3671,12 @@ static int builtin_for(parser_t &parser, wchar_t **argv)
int argc = builtin_count_args(argv);
int res=STATUS_BUILTIN_ERROR;
/* Hackish - if we have no arguments other than the command, we are a "naked invocation" and we just print help */
if (argc == 1)
{
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_ERROR;
}
if (argc < 3)
{
@ -3755,6 +4063,13 @@ static int builtin_switch(parser_t &parser, wchar_t **argv)
{
int res=STATUS_BUILTIN_OK;
int argc = builtin_count_args(argv);
/* Hackish - if we have no arguments other than the command, we are a "naked invocation" and we just print help */
if (argc == 1)
{
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_ERROR;
}
if (argc != 2)
{
@ -3960,6 +4275,49 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
return STATUS_BUILTIN_ERROR;
}
#pragma mark Simulator
int builtin_parse(parser_t &parser, wchar_t **argv)
{
struct sigaction act;
sigemptyset(& act.sa_mask);
act.sa_flags=0;
act.sa_handler=SIG_DFL;
sigaction(SIGINT, &act, 0);
std::vector<char> txt;
for (;;)
{
char buff[256];
ssize_t amt = read_loop(builtin_stdin, buff, sizeof buff);
if (amt <= 0) break;
txt.insert(txt.end(), buff, buff + amt);
}
if (! txt.empty())
{
const wcstring src = str2wcstring(&txt.at(0), txt.size());
parse_node_tree_t parse_tree;
parse_error_list_t errors;
bool success = parse_tree_from_string(src, parse_flag_none, &parse_tree, &errors, true);
if (! success)
{
stdout_buffer.append(L"Parsing failed:\n");
for (size_t i=0; i < errors.size(); i++)
{
stdout_buffer.append(errors.at(i).describe(src));
stdout_buffer.push_back(L'\n');
}
stdout_buffer.append(L"(Reparsed with continue after error)\n");
parse_tree.clear();
errors.clear();
parse_tree_from_string(src, parse_flag_continue_after_error, &parse_tree, &errors, true);
}
const wcstring dump = parse_dump_tree(parse_tree, src);
stdout_buffer.append(dump);
}
return STATUS_BUILTIN_OK;
}
/*
END OF BUILTIN COMMANDS
@ -3975,6 +4333,7 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
static const builtin_data_t builtin_datas[]=
{
{ L"[", &builtin_test, N_(L"Test a condition") },
{ L"__fish_parse", &builtin_parse, N_(L"Try out the new parser") },
{ L"and", &builtin_generic, N_(L"Execute command if previous command suceeded") },
{ L"begin", &builtin_begin, N_(L"Create a block of code") },
{ L"bg", &builtin_bg, N_(L"Send job to background") },
@ -4119,7 +4478,7 @@ void builtin_get_names(std::vector<completion_t> &list)
{
for (size_t i=0; i < BUILTIN_COUNT; i++)
{
list.push_back(completion_t(builtin_datas[i].name));
append_completion(list, builtin_datas[i].name);
}
}
@ -4166,4 +4525,3 @@ void builtin_pop_io(parser_t &parser)
builtin_stdin = 0;
}
}

View file

@ -176,7 +176,10 @@ const wchar_t *builtin_complete_get_temporary_buffer();
Run the __fish_print_help function to obtain the help information
for the specified command.
*/
wcstring builtin_help_get(parser_t &parser, const wchar_t *cmd);
/** Defines a function, like builtin_function. Returns 0 on success. args should NOT contain 'function' as the first argument. */
int define_function(parser_t &parser, const wcstring_list_t &args, const wcstring &contents, wcstring *out_err);
#endif

View file

@ -143,17 +143,13 @@ static void write_part(const wchar_t *begin,
int cut_at_cursor,
int tokenize)
{
wcstring out;
wchar_t *buff;
size_t pos;
pos = get_cursor_pos()-(begin-get_buffer());
size_t pos = get_cursor_pos()-(begin-get_buffer());
if (tokenize)
{
buff = wcsndup(begin, end-begin);
wchar_t *buff = wcsndup(begin, end-begin);
// fwprintf( stderr, L"Subshell: %ls, end char %lc\n", buff, *end );
out.clear();
wcstring out;
tokenizer_t tok(buff, TOK_ACCEPT_UNFINISHED);
for (; tok_has_next(&tok); tok_next(&tok))
{

View file

@ -290,7 +290,6 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
int result_mode=SHARED;
int remove = 0;
int authoritative = -1;
int flags = COMPLETE_AUTO_SPACE;
wcstring short_opt;
wcstring_list_t gnu_opt, old_opt;
@ -497,15 +496,19 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
{
if (condition && wcslen(condition))
{
if (parser.test(condition))
const wcstring condition_string = condition;
parse_error_list_t errors;
if (parse_util_detect_errors(condition_string, &errors))
{
append_format(stderr_buffer,
L"%ls: Condition '%ls' contained a syntax error\n",
L"%ls: Condition '%ls' contained a syntax error",
argv[0],
condition);
parser.test(condition, NULL, &stderr_buffer, argv[0]);
for (size_t i=0; i < errors.size(); i++)
{
append_format(stderr_buffer, L"\n%s: ", argv[0]);
stderr_buffer.append(errors.at(i).describe(condition_string));
}
res = true;
}
}
@ -596,6 +599,8 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
}
else
{
int flags = COMPLETE_AUTO_SPACE;
if (remove)
{
builtin_complete_remove(cmd,

View file

@ -164,7 +164,6 @@ static int builtin_jobs(parser_t &parser, wchar_t **argv)
int found=0;
int mode=JOBS_DEFAULT;
int print_last = 0;
const job_t *j;
argc = builtin_count_args(argv);
woptind=0;
@ -305,7 +304,7 @@ static int builtin_jobs(parser_t &parser, wchar_t **argv)
return 1;
}
j = job_get_from_pid(pid);
const job_t *j = job_get_from_pid(pid);
if (j && !job_is_completed(j))
{

View file

@ -697,7 +697,6 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
/*
Slice mode
*/
size_t idx_count, val_count;
std::vector<long> indexes;
wcstring_list_t result;
@ -713,9 +712,9 @@ static int builtin_set(parser_t &parser, wchar_t **argv)
retcode = 1;
break;
}
val_count = argc-woptind-1;
idx_count = indexes.size();
size_t idx_count = indexes.size();
size_t val_count = argc-woptind-1;
if (!erase)
{

View file

@ -105,7 +105,7 @@ void show_stackframe()
return;
void *trace[32];
int i, trace_size = 0;
int trace_size = 0;
trace_size = backtrace(trace, 32);
char **messages = backtrace_symbols(trace, trace_size);
@ -113,7 +113,7 @@ void show_stackframe()
if (messages)
{
debug(0, L"Backtrace:");
for (i=0; i<trace_size; i++)
for (int i=0; i<trace_size; i++)
{
fwprintf(stderr, L"%s\n", messages[i]);
}
@ -508,7 +508,7 @@ const wchar_t *wcsfuncname(const wchar_t *str)
}
int wcsvarchr(wchar_t chr)
bool wcsvarchr(wchar_t chr)
{
return iswalnum(chr) || chr == L'_';
}
@ -761,7 +761,7 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para
errno = errno_old;
}
void format_long_safe(char buff[128], long val)
void format_long_safe(char buff[64], long val)
{
if (val == 0)
{
@ -795,7 +795,7 @@ void format_long_safe(char buff[128], long val)
}
}
void format_long_safe(wchar_t buff[128], long val)
void format_long_safe(wchar_t buff[64], long val)
{
if (val == 0)
{
@ -830,19 +830,18 @@ void format_long_safe(wchar_t buff[128], long val)
void write_screen(const wcstring &msg, wcstring &buff)
{
const wchar_t *start, *pos;
int line_width = 0;
int tok_width = 0;
int screen_width = common_get_width();
if (screen_width)
{
start = pos = msg.c_str();
const wchar_t *start = msg.c_str();
const wchar_t *pos = start;
while (1)
{
int overflow = 0;
tok_width=0;
int tok_width=0;
/*
Tokenize on whitespace, and also calculate the width of the token

View file

@ -348,8 +348,8 @@ void format_size_safe(char buff[128], unsigned long long sz);
void debug_safe(int level, const char *msg, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL, const char *param5 = NULL, const char *param6 = NULL, const char *param7 = NULL, const char *param8 = NULL, const char *param9 = NULL, const char *param10 = NULL, const char *param11 = NULL, const char *param12 = NULL);
/** Writes out a long safely */
void format_long_safe(char buff[128], long val);
void format_long_safe(wchar_t buff[128], long val);
void format_long_safe(char buff[64], long val);
void format_long_safe(wchar_t buff[64], long val);
template<typename T>
@ -612,10 +612,10 @@ const wchar_t *wcsfuncname(const wchar_t *str);
/**
Test if the given string is valid in a variable name
\return 1 if this is a valid name, 0 otherwise
\return true if this is a valid name, false otherwise
*/
int wcsvarchr(wchar_t chr);
bool wcsvarchr(wchar_t chr);
/**

View file

@ -44,6 +44,7 @@
#include "parser_keywords.h"
#include "wutil.h"
#include "path.h"
#include "parse_tree.h"
#include "iothread.h"
/*
@ -465,7 +466,13 @@ void completion_autoload_t::command_removed(const wcstring &cmd)
/** Create a new completion entry */
void append_completion(std::vector<completion_t> &completions, const wcstring &comp, const wcstring &desc, complete_flags_t flags, string_fuzzy_match_t match)
{
completions.push_back(completion_t(comp, desc, match, flags));
/* If we just constructed the completion and used push_back, we would get two string copies. Try to avoid that by making a stubby completion in the vector first, and then copying our string in. */
completions.push_back(completion_t(wcstring()));
completion_t *last = &completions.back();
last->completion = comp;
last->description = desc;
last->match = match;
last->flags = flags;
}
/**
@ -1190,7 +1197,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool
wcstring_list_t names = function_get_names(str_cmd.at(0) == L'_');
for (size_t i=0; i < names.size(); i++)
{
possible_comp.push_back(completion_t(names.at(i)));
append_completion(possible_comp, names.at(i));
}
this->complete_strings(str_cmd, 0, &complete_function_desc, possible_comp, 0);
@ -1229,7 +1236,7 @@ void completer_t::complete_from_args(const wcstring &str,
std::vector<completion_t> possible_comp;
bool is_autosuggest = (this->type() == COMPLETE_AUTOSUGGEST);
parser_t parser(is_autosuggest ? PARSER_TYPE_COMPLETIONS_ONLY : PARSER_TYPE_GENERAL, false);
parser_t parser(is_autosuggest ? PARSER_TYPE_COMPLETIONS_ONLY : PARSER_TYPE_GENERAL, false /* don't show errors */);
/* If type is COMPLETE_AUTOSUGGEST, it means we're on a background thread, so don't call proc_push_interactive */
if (! is_autosuggest)
@ -1360,7 +1367,9 @@ struct local_options_t
bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spopt, const wcstring &sstr, bool use_switches)
{
const wchar_t * const cmd_orig = scmd_orig.c_str(), * const popt = spopt.c_str(), * const str = sstr.c_str();
const wchar_t * const cmd_orig = scmd_orig.c_str();
const wchar_t * const popt = spopt.c_str();
const wchar_t * const str = sstr.c_str();
bool use_common=1, use_files=1;
@ -1475,7 +1484,7 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
{
if (o->result_mode & NO_COMMON) use_common = false;
if (o->result_mode & NO_FILES) use_files = false;
complete_from_args(str, o->comp.c_str(), o->localized_desc(), o->flags);
complete_from_args(str, o->comp, o->localized_desc(), o->flags);
}
}
@ -1688,7 +1697,7 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset)
desc = format_string(COMPLETE_VAR_DESC_VAL, value.c_str());
}
append_completion(this->completions, comp.c_str(), desc.c_str(), flags, match);
append_completion(this->completions, comp, desc, flags, match);
res = true;
}
@ -1788,226 +1797,155 @@ bool completer_t::try_complete_user(const wcstring &str)
return res;
}
void complete(const wcstring &cmd, std::vector<completion_t> &comps, completion_request_flags_t flags)
void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps, completion_request_flags_t flags)
{
/* Determine the innermost subcommand */
const wchar_t *cmdsubst_begin, *cmdsubst_end;
parse_util_cmdsubst_extent(cmd_with_subcmds.c_str(), cmd_with_subcmds.size(), &cmdsubst_begin, &cmdsubst_end);
assert(cmdsubst_begin != NULL && cmdsubst_end != NULL && cmdsubst_end >= cmdsubst_begin);
const wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin);
/* Make our completer */
completer_t completer(cmd, flags);
const wchar_t *tok_begin, *tok_end, *cmdsubst_begin, *cmdsubst_end, *prev_begin, *prev_end;
wcstring current_token, prev_token;
wcstring current_command;
int on_command=0;
size_t pos;
const size_t pos = cmd.size();
bool done=false;
int use_command = 1;
int use_function = 1;
int use_builtin = 1;
int had_ddash = 0;
// debug( 1, L"Complete '%ls'", cmd );
size_t cursor_pos = cmd.size();
bool use_command = 1;
bool use_function = 1;
bool use_builtin = 1;
// debug( 1, L"Complete '%ls'", cmd );
const wchar_t *cmd_cstr = cmd.c_str();
parse_util_cmdsubst_extent(cmd_cstr, cursor_pos, &cmdsubst_begin, &cmdsubst_end);
parse_util_token_extent(cmd_cstr, cursor_pos, &tok_begin, &tok_end, &prev_begin, &prev_end);
if (!cmdsubst_begin)
done=1;
const wchar_t *tok_begin = NULL, *prev_begin = NULL, *prev_end = NULL;
parse_util_token_extent(cmd_cstr, cmd.size(), &tok_begin, NULL, &prev_begin, &prev_end);
/**
If we are completing a variable name or a tilde expansion user
name, we do that and return. No need for any other completions.
*/
If we are completing a variable name or a tilde expansion user
name, we do that and return. No need for any other completions.
*/
const wcstring current_token = tok_begin;
if (!done)
{
wcstring tmp = tok_begin;
done = completer.try_complete_variable(tmp) || completer.try_complete_user(tmp);
done = completer.try_complete_variable(current_token) || completer.try_complete_user(current_token);
}
if (!done)
{
pos = cursor_pos-(cmdsubst_begin-cmd_cstr);
const wcstring buff = wcstring(cmdsubst_begin, cmdsubst_end-cmdsubst_begin);
int had_cmd=0;
int end_loop=0;
tokenizer_t tok(buff.c_str(), TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS);
while (tok_has_next(&tok) && !end_loop)
//const size_t prev_token_len = (prev_begin ? prev_end - prev_begin : 0);
//const wcstring prev_token(prev_begin, prev_token_len);
parse_node_tree_t tree;
parse_tree_from_string(cmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &tree, NULL);
/* Find the plain statement that contains the position */
const parse_node_t *plain_statement = tree.find_node_matching_source_location(symbol_plain_statement, pos, NULL);
if (plain_statement != NULL)
{
switch (tok_last_type(&tok))
assert(plain_statement->has_source() && plain_statement->type == symbol_plain_statement);
/* Get the command node */
const parse_node_t *cmd_node = tree.get_child(*plain_statement, 0, parse_token_type_string);
/* Get the actual command string */
if (cmd_node != NULL)
current_command = cmd_node->get_source(cmd);
/* Check the decoration */
switch (tree.decoration_for_plain_statement(*plain_statement))
{
case TOK_STRING:
case parse_statement_decoration_none:
use_command = true;
use_function = true;
use_builtin = true;
break;
case parse_statement_decoration_command:
use_command = true;
use_function = false;
use_builtin = false;
break;
case parse_statement_decoration_builtin:
use_command = false;
use_function = false;
use_builtin = true;
break;
}
if (cmd_node && cmd_node->location_in_or_at_end_of_source_range(pos))
{
/* Complete command filename */
completer.complete_cmd(current_token, use_function, use_builtin, use_command);
}
else
{
/* Get all the arguments */
const parse_node_tree_t::parse_node_list_t all_arguments = tree.find_nodes(*plain_statement, symbol_argument);
/* See whether we are in an argument. We may also be in a redirection, or nothing at all. */
size_t matching_arg_index = -1;
for (size_t i=0; i < all_arguments.size(); i++)
{
const wcstring ncmd = tok_last(&tok);
int is_ddash = (ncmd == L"--") && ((tok_get_pos(&tok)+2) < (long)pos);
if (!had_cmd)
const parse_node_t *node = all_arguments.at(i);
if (node->location_in_or_at_end_of_source_range(pos))
{
if (parser_keywords_is_subcommand(ncmd))
matching_arg_index = i;
break;
}
}
bool had_ddash = false;
wcstring current_argument, previous_argument;
if (matching_arg_index != (size_t)(-1))
{
/* Get the current argument and the previous argument, if we have one */
current_argument = all_arguments.at(matching_arg_index)->get_source(cmd);
if (matching_arg_index > 0)
previous_argument = all_arguments.at(matching_arg_index - 1)->get_source(cmd);
/* Check to see if we have a preceding double-dash */
for (size_t i=0; i < matching_arg_index; i++)
{
if (all_arguments.at(i)->get_source(cmd) == L"--")
{
if (ncmd == L"builtin")
{
use_function = 0;
use_command = 0;
use_builtin = 1;
}
else if (ncmd == L"command")
{
use_command = 1;
use_function = 0;
use_builtin = 0;
}
had_ddash = true;
break;
}
if (!is_ddash ||
((use_command && use_function && use_builtin)))
{
current_command = ncmd;
size_t token_end = tok_get_pos(&tok) + ncmd.size();
on_command = (pos <= token_end);
had_cmd=1;
}
}
else
{
if (is_ddash)
{
had_ddash = 1;
}
}
break;
}
case TOK_END:
case TOK_PIPE:
case TOK_BACKGROUND:
bool do_file = false;
wcstring current_command_unescape, previous_argument_unescape, current_argument_unescape;
if (unescape_string(current_command, &current_command_unescape, UNESCAPE_DEFAULT) &&
unescape_string(previous_argument, &previous_argument_unescape, UNESCAPE_DEFAULT) &&
unescape_string(current_argument, &current_argument_unescape, UNESCAPE_INCOMPLETE))
{
had_cmd=0;
had_ddash = 0;
use_command = 1;
use_function = 1;
use_builtin = 1;
break;
}
case TOK_ERROR:
{
end_loop=1;
break;
}
default:
{
break;
do_file = completer.complete_param(current_command_unescape,
previous_argument_unescape,
current_argument_unescape,
!had_ddash);
}
/* If we have found no command specific completions at all, fall back to using file completions. */
if (completer.empty())
do_file = true;
/* And if we're autosuggesting, and the token is empty, don't do file suggestions */
if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_argument_unescape.empty())
do_file = false;
/* This function wants the unescaped string */
completer.complete_param_expand(current_token, do_file);
}
if (tok_get_pos(&tok) >= (long)pos)
{
end_loop=1;
}
tok_next(&tok);
}
/*
Get the string to complete
*/
current_token.assign(tok_begin, cursor_pos-(tok_begin-cmd_cstr));
if (prev_begin)
{
prev_token.assign(prev_begin, prev_end - prev_begin);
}
else
{
prev_token.clear();
}
// debug( 0, L"on_command: %d, %ls %ls\n", on_command, current_command, current_token );
/*
Check if we are using the 'command' or 'builtin' builtins
_and_ we are writing a switch instead of a command. In that
case, complete using the builtins completions, not using a
subcommand.
*/
if ((on_command || current_token == L"--") &&
string_prefixes_string(L"-", current_token) &&
!(use_command && use_function && use_builtin))
{
if (use_command == 0)
current_command = L"builtin";
else
current_command = L"command";
had_cmd = 1;
on_command = 0;
}
/*
Use command completions if in between commands
*/
if (!had_cmd)
{
on_command=1;
}
if (on_command)
{
/* Complete command filename */
completer.complete_cmd(current_token, use_function, use_builtin, use_command);
}
else
{
bool do_file = false;
wcstring current_command_unescape, prev_token_unescape, current_token_unescape;
if (unescape_string(current_command, &current_command_unescape, UNESCAPE_DEFAULT) &&
unescape_string(prev_token, &prev_token_unescape, UNESCAPE_DEFAULT) &&
unescape_string(current_token, &current_token_unescape, UNESCAPE_INCOMPLETE))
{
do_file = completer.complete_param(current_command_unescape,
prev_token_unescape,
current_token_unescape,
!had_ddash);
}
/* If we have found no command specific completions at
all, fall back to using file completions.
*/
if (completer.empty())
do_file = true;
/* If we're autosuggesting, and the token is empty, don't do file suggestions */
if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_token_unescape.empty())
do_file = false;
/*
This function wants the unescaped string
*/
completer.complete_param_expand(current_token, do_file);
}
}
comps = completer.get_completions();
}

View file

@ -124,7 +124,7 @@ public:
int flags;
/* Construction. Note: defining these so that they are not inlined reduces the executable size. */
completion_t(const wcstring &comp, const wcstring &desc = L"", string_fuzzy_match_t match = string_fuzzy_match_t(fuzzy_match_exact), int flags_val = 0);
completion_t(const wcstring &comp, const wcstring &desc = wcstring(), string_fuzzy_match_t match = string_fuzzy_match_t(fuzzy_match_exact), int flags_val = 0);
completion_t(const completion_t &);
completion_t &operator=(const completion_t &);
@ -268,7 +268,7 @@ void complete_load(const wcstring &cmd, bool reload);
\param flags completion flags
*/
void append_completion(std::vector<completion_t> &completions, const wcstring &comp, const wcstring &desc = L"", int flags = 0, string_fuzzy_match_t match = string_fuzzy_match_t(fuzzy_match_exact));
void append_completion(std::vector<completion_t> &completions, const wcstring &comp, const wcstring &desc = wcstring(), int flags = 0, string_fuzzy_match_t match = string_fuzzy_match_t(fuzzy_match_exact));
/* Function used for testing */
void complete_set_variable_names(const wcstring_list_t *names);

View file

@ -299,7 +299,6 @@ static bool var_is_locale(const wcstring &key)
static void handle_locale()
{
const env_var_t lc_all = env_get_string(L"LC_ALL");
int i;
const wcstring old_locale = wsetlocale(LC_MESSAGES, NULL);
/*
@ -330,7 +329,7 @@ static void handle_locale()
wsetlocale(LC_ALL, lang.c_str());
}
for (i=2; locale_variable[i]; i++)
for (int i=2; locale_variable[i]; i++)
{
const env_var_t val = env_get_string(locale_variable[i]);
@ -479,7 +478,7 @@ static void env_set_defaults()
if (pw->pw_name != NULL)
{
const wcstring wide_name = str2wcstring(pw->pw_name);
env_set(L"USER", NULL, ENV_GLOBAL);
env_set(L"USER", wide_name.c_str(), ENV_GLOBAL);
}
}
@ -892,6 +891,7 @@ int env_set(const wcstring &key, const wchar_t *val, int var_mode)
if (!is_universal)
{
event_t ev = event_t::variable_event(key);
ev.arguments.reserve(3);
ev.arguments.push_back(L"VARIABLE");
ev.arguments.push_back(L"SET");
ev.arguments.push_back(key);

View file

@ -426,8 +426,6 @@ void env_universal_barrier()
void env_universal_set(const wcstring &name, const wcstring &value, bool exportv)
{
message_t *msg;
if (!s_env_univeral_inited)
return;
@ -439,7 +437,7 @@ void env_universal_set(const wcstring &name, const wcstring &value, bool exportv
}
else
{
msg = create_message(exportv?SET_EXPORT:SET,
message_t *msg = create_message(exportv?SET_EXPORT:SET,
name.c_str(),
value.c_str());
@ -459,7 +457,6 @@ int env_universal_remove(const wchar_t *name)
{
int res;
message_t *msg;
if (!s_env_univeral_inited)
return 1;
@ -476,7 +473,7 @@ int env_universal_remove(const wchar_t *name)
}
else
{
msg= create_message(ERASE, name, 0);
message_t *msg = create_message(ERASE, name, 0);
msg->count=1;
env_universal_server.unsent.push(msg);
env_universal_barrier();

View file

@ -568,9 +568,6 @@ static void event_fire_internal(const event_t &event)
*/
static void event_fire_delayed()
{
size_t i;
/*
If is_event is one, we are running the event-handler non-recursively.
@ -582,7 +579,7 @@ static void event_fire_delayed()
{
event_list_t new_blocked;
for (i=0; i<blocked.size(); i++)
for (size_t i=0; i<blocked.size(); i++)
{
event_t *e = blocked.at(i);
if (event_is_blocked(*e))

View file

@ -394,12 +394,13 @@ static void io_cleanup_fds(const std::vector<int> &opened_fds)
repeatedly reopened for every command in the block, which would
reset the cursor position.
\return the transmogrified chain on sucess, or 0 on failiure
\return true on success, false on failure. Returns the output chain and opened_fds by reference
*/
static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, std::vector<int> &out_opened_fds)
static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t *out_chain, std::vector<int> *out_opened_fds)
{
ASSERT_IS_MAIN_THREAD();
assert(out_chain.empty());
assert(out_chain != NULL && out_opened_fds != NULL);
assert(out_chain->empty());
/* Just to be clear what we do for an empty chain */
if (in_chain.empty())
@ -479,8 +480,8 @@ static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, s
if (success)
{
/* Yay */
out_chain.swap(result_chain);
out_opened_fds.swap(opened_fds);
out_chain->swap(result_chain);
out_opened_fds->swap(opened_fds);
}
else
{
@ -496,19 +497,24 @@ static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, s
Morph an io redirection chain into redirections suitable for
passing to eval, call eval, and clean up morphed redirections.
\param def the code to evaluate
\param def the code to evaluate, or the empty string if none
\param node_offset the offset of the node to evalute, or NODE_OFFSET_INVALID
\param block_type the type of block to push on evaluation
\param io the io redirections to be performed on this block
*/
static void internal_exec_helper(parser_t &parser,
const wchar_t *def,
const wcstring &def,
node_offset_t node_offset,
enum block_type_t block_type,
const io_chain_t &ios)
{
// If we have a valid node offset, then we must not have a string to execute
assert(node_offset == NODE_OFFSET_INVALID || def.empty());
io_chain_t morphed_chain;
std::vector<int> opened_fds;
bool transmorgrified = io_transmogrify(ios, morphed_chain, opened_fds);
bool transmorgrified = io_transmogrify(ios, &morphed_chain, &opened_fds);
int is_block_old=is_block;
is_block=1;
@ -524,7 +530,14 @@ static void internal_exec_helper(parser_t &parser,
signal_unblock();
parser.eval(def, morphed_chain, block_type);
if (node_offset == NODE_OFFSET_INVALID)
{
parser.eval(def, morphed_chain, block_type);
}
else
{
parser.eval_block_node(node_offset, morphed_chain, block_type);
}
signal_block();
@ -564,6 +577,12 @@ static bool can_use_posix_spawn_for_job(const job_t *job, const process_t *proce
/* What exec does if no_exec is set. This only has to handle block pushing and popping. See #624. */
static void exec_no_exec(parser_t &parser, const job_t *job)
{
if (parser_use_ast())
{
/* With the new parser, commands aren't responsible for pushing / popping blocks, so there's nothing to do */
return;
}
/* Hack hack hack. If this is an 'end' job, then trigger a pop. If this is a job that would create a block, trigger a push. See #624 */
const process_t *p = job->first_process;
if (p && p->type == INTERNAL_BUILTIN)
@ -682,7 +701,7 @@ void exec_job(parser_t &parser, job_t *j)
j->first_process->completed=1;
return;
}
assert(0 && "This should be unreachable");
}
signal_block();
@ -807,7 +826,6 @@ void exec_job(parser_t &parser, job_t *j)
{
pipe_write.reset(new io_pipe_t(p->pipe_write_fd, false));
process_net_io_chain.push_back(pipe_write);
}
/* The explicit IO redirections associated with the process */
@ -926,7 +944,7 @@ void exec_job(parser_t &parser, job_t *j)
if (! exec_error)
{
internal_exec_helper(parser, def.c_str(), TOP, process_net_io_chain);
internal_exec_helper(parser, def, NODE_OFFSET_INVALID, TOP, process_net_io_chain);
}
parser.allow_function();
@ -936,12 +954,14 @@ void exec_job(parser_t &parser, job_t *j)
}
case INTERNAL_BLOCK:
case INTERNAL_BLOCK_NODE:
{
if (p->next)
{
block_output_io_buffer.reset(io_buffer_t::create(0));
if (block_output_io_buffer.get() == NULL)
{
/* We failed (e.g. no more fds could be created). */
exec_error = true;
job_mark_process_as_failed(j, p);
}
@ -954,12 +974,21 @@ void exec_job(parser_t &parser, job_t *j)
if (! exec_error)
{
internal_exec_helper(parser, p->argv0(), TOP, process_net_io_chain);
if (p->type == INTERNAL_BLOCK)
{
/* The block contents (as in, fish code) are stored in argv0 (ugh) */
assert(p->argv0() != NULL);
internal_exec_helper(parser, p->argv0(), NODE_OFFSET_INVALID, TOP, process_net_io_chain);
}
else
{
assert(p->type == INTERNAL_BLOCK_NODE);
internal_exec_helper(parser, wcstring(), p->internal_block_node, TOP, process_net_io_chain);
}
}
break;
}
case INTERNAL_BUILTIN:
{
int builtin_stdin=0;
@ -1104,6 +1133,20 @@ void exec_job(parser_t &parser, job_t *j)
}
break;
}
case EXTERNAL:
/* External commands are handled in the next switch statement below */
break;
case INTERNAL_EXEC:
/* We should have handled exec up above */
assert(0 && "INTERNAL_EXEC process found in pipeline, where it should never be. Aborting.");
break;
case INTERNAL_BUFFER:
/* Internal buffers are handled in the next switch statement below */
break;
}
if (exec_error)
@ -1115,6 +1158,7 @@ void exec_job(parser_t &parser, job_t *j)
{
case INTERNAL_BLOCK:
case INTERNAL_BLOCK_NODE:
case INTERNAL_FUNCTION:
{
int status = proc_get_last_status();
@ -1131,7 +1175,7 @@ void exec_job(parser_t &parser, job_t *j)
No buffer, so we exit directly. This means we
have to manually set the exit status.
*/
if (p->next == 0)
if (p->next == NULL)
{
proc_set_last_status(job_get_flag(j, JOB_NEGATE)?(!status):status);
}
@ -1463,7 +1507,13 @@ void exec_job(parser_t &parser, job_t *j)
break;
}
case INTERNAL_EXEC:
{
/* We should have handled exec up above */
assert(0 && "INTERNAL_EXEC process found in pipeline, where it should never be. Aborting.");
break;
}
}
if (p->type == INTERNAL_BUILTIN)
@ -1531,6 +1581,8 @@ static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, boo
int prev_subshell = is_subshell;
const int prev_status = proc_get_last_status();
char sep=0;
//fprintf(stderr, "subcmd %ls\n", cmd.c_str());
const env_var_t ifs = env_get_string(L"IFS");

View file

@ -785,7 +785,15 @@ static int expand_pid(const wcstring &instr_with_sep,
expand_flags_t flags,
std::vector<completion_t> &out)
{
/* Hack. If there's no INTERNAL_SEP and no PROCESS_EXPAND, then there's nothing to do. Check out this "null terminated string." */
const wchar_t some_chars[] = {INTERNAL_SEPARATOR, PROCESS_EXPAND, L'\0'};
if (instr_with_sep.find_first_of(some_chars) == wcstring::npos)
{
/* Nothing to do */
append_completion(out, instr_with_sep);
return 1;
}
/* expand_string calls us with internal separators in instr...sigh */
wcstring instr = instr_with_sep;
remove_internal_separator(instr, false);
@ -1372,7 +1380,7 @@ static int expand_brackets(parser_t &parser, const wcstring &instr, int flags, s
/**
Perform cmdsubst expansion
*/
static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<completion_t> &outList)
static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<completion_t> &out_list)
{
wchar_t *paran_begin=0, *paran_end=0;
std::vector<wcstring> sub_res;
@ -1390,7 +1398,7 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
L"Mismatched parenthesis");
return 0;
case 0:
outList.push_back(completion_t(input));
append_completion(out_list, input);
return 1;
case 1:
@ -1455,15 +1463,15 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
*/
for (i=0; i<sub_res.size(); i++)
{
wcstring sub_item = sub_res.at(i);
wcstring sub_item2 = escape_string(sub_item, 1);
const wcstring &sub_item = sub_res.at(i);
const wcstring sub_item2 = escape_string(sub_item, 1);
wcstring whole_item;
for (j=0; j < tail_expand.size(); j++)
{
wcstring whole_item;
wcstring tail_item = tail_expand.at(j).completion;
whole_item.clear();
const wcstring &tail_item = tail_expand.at(j).completion;
//sb_append_substring( &whole_item, in, len1 );
whole_item.append(in, paran_begin-in);
@ -1481,7 +1489,7 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
whole_item.append(tail_item);
//al_push( out, whole_item.buff );
outList.push_back(completion_t(whole_item));
append_completion(out_list, whole_item);
}
}
@ -1602,6 +1610,31 @@ static void unexpand_tildes(const wcstring &input, std::vector<completion_t> *co
}
}
// If the given path contains the user's home directory, replace that with a tilde
// We don't try to be smart about case insensitivity, etc.
wcstring replace_home_directory_with_tilde(const wcstring &str)
{
// only absolute paths get this treatment
wcstring result = str;
if (string_prefixes_string(L"/", result))
{
wcstring home_directory = L"~";
expand_tilde(home_directory);
if (! string_suffixes_string(L"/", home_directory))
{
home_directory.push_back(L'/');
}
// Now check if the home_directory prefixes the string
if (string_prefixes_string(home_directory, result))
{
// Success
result.replace(0, home_directory.size(), L"~/");
}
}
return result;
}
/**
Remove any internal separators. Also optionally convert wildcard characters to
regular equivalents. This is done to support EXPAND_SKIP_WILDCARDS.
@ -1640,7 +1673,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
if ((!(flags & ACCEPT_INCOMPLETE)) && expand_is_clean(input.c_str()))
{
output.push_back(completion_t(input));
append_completion(output, input);
return EXPAND_OK;
}
@ -1656,7 +1689,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
parser.error(CMDSUBST_ERROR, -1, L"Command substitutions not allowed");
return EXPAND_ERROR;
}
in->push_back(completion_t(input));
append_completion(*in, input);
}
else
{
@ -1684,7 +1717,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
next[i] = L'$';
}
}
out->push_back(completion_t(next));
append_completion(*out, next);
}
else
{
@ -1700,7 +1733,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
for (i=0; i < in->size(); i++)
{
wcstring next = in->at(i).completion;
const wcstring &next = in->at(i).completion;
if (!expand_brackets(parser, next, flags, *out))
{
@ -1720,7 +1753,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
if (flags & ACCEPT_INCOMPLETE)
{
if (next[0] == PROCESS_EXPAND)
if (! next.empty() && next.at(0) == PROCESS_EXPAND)
{
/*
If process expansion matches, we are not
@ -1733,7 +1766,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
}
else
{
out->push_back(completion_t(next));
append_completion(*out, next);
}
}
else
@ -1815,7 +1848,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
{
if (!(flags & ACCEPT_INCOMPLETE))
{
out->push_back(completion_t(next_str));
append_completion(*out, next_str);
}
}
}
@ -1842,7 +1875,7 @@ bool expand_one(wcstring &string, expand_flags_t flags)
return true;
}
if (expand_string(string, completions, flags))
if (expand_string(string, completions, flags | EXPAND_NO_DESCRIPTIONS))
{
if (completions.size() == 1)
{
@ -1945,19 +1978,19 @@ bool fish_openSUSE_dbus_hack_hack_hack_hack(std::vector<completion_t> *args)
val.resize(last_good + 1);
args->clear();
args->push_back(completion_t(L"set"));
append_completion(*args, L"set");
if (key == L"DBUS_SESSION_BUS_ADDRESS")
args->push_back(completion_t(L"-x"));
args->push_back(completion_t(key));
args->push_back(completion_t(val));
append_completion(*args, L"-x");
append_completion(*args, key);
append_completion(*args, val);
result = true;
}
else if (string_prefixes_string(L"export DBUS_SESSION_BUS_ADDRESS;", cmd))
{
/* Nothing, we already exported it */
args->clear();
args->push_back(completion_t(L"echo"));
args->push_back(completion_t(L"-n"));
append_completion(*args, L"echo");
append_completion(*args, L"-n");
result = true;
}
}

View file

@ -176,6 +176,9 @@ wcstring expand_escape_variable(const wcstring &in);
*/
void expand_tilde(wcstring &input);
/** Perform the opposite of tilde expansion on the string, which is modified in place */
wcstring replace_home_directory_with_tilde(const wcstring &str);
/**
Test if the specified argument is clean, i.e. it does not contain
any tokens which need to be expanded or otherwise altered. Clean

View file

@ -65,6 +65,7 @@
D033781115DC6D4C00A634BA /* completions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02715D1FEA100B9DB63 /* completions */; };
D033781215DC6D5200A634BA /* functions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02815D1FEA100B9DB63 /* functions */; };
D033781315DC6D5400A634BA /* tools in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02915D1FEA100B9DB63 /* tools */; };
D052D80B1868F7FC003ABCBD /* parse_execution.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D052D8091868F7FC003ABCBD /* parse_execution.cpp */; };
D07B247315BCC15700D4ADB4 /* add-shell in Resources */ = {isa = PBXBuildFile; fileRef = D07B247215BCC15700D4ADB4 /* add-shell */; };
D07B247615BCC4BE00D4ADB4 /* install.sh in Resources */ = {isa = PBXBuildFile; fileRef = D07B247515BCC4BE00D4ADB4 /* install.sh */; };
D07D266A15E33B86009E43F6 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0C4FD9415A7D7EE00212EF1 /* config.fish */; };
@ -73,8 +74,50 @@
D07D266E15E33B86009E43F6 /* tools in Copy Files */ = {isa = PBXBuildFile; fileRef = D025C02915D1FEA100B9DB63 /* tools */; };
D07D267215E34171009E43F6 /* config.fish in Copy Files */ = {isa = PBXBuildFile; fileRef = D0CBD580159EE48F0024809C /* config.fish */; };
D0879AC816BF9AAB00E98E56 /* fish_term_icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = D0879AC616BF9A1A00E98E56 /* fish_term_icon.icns */; };
D08A329417B4458D00F3A533 /* fish_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D08A329317B4458D00F3A533 /* fish_tests.cpp */; };
D08A329517B445C200F3A533 /* function.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854413B3ACEE0099B651 /* function.cpp */; };
D08A329617B445FD00F3A533 /* builtin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853513B3ACEE0099B651 /* builtin.cpp */; };
D08A329717B4463B00F3A533 /* complete.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853713B3ACEE0099B651 /* complete.cpp */; };
D08A329817B4463B00F3A533 /* env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853A13B3ACEE0099B651 /* env.cpp */; };
D08A329917B4463B00F3A533 /* exec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853C13B3ACEE0099B651 /* exec.cpp */; };
D08A329A17B4463B00F3A533 /* expand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853D13B3ACEE0099B651 /* expand.cpp */; };
D08A329B17B4463B00F3A533 /* highlight.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854713B3ACEE0099B651 /* highlight.cpp */; };
D08A329C17B4463B00F3A533 /* history.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854813B3ACEE0099B651 /* history.cpp */; };
D08A329D17B4463B00F3A533 /* kill.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854F13B3ACEE0099B651 /* kill.cpp */; };
D08A329E17B4463B00F3A533 /* parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855413B3ACEE0099B651 /* parser.cpp */; };
D08A329F17B4463B00F3A533 /* proc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855713B3ACEE0099B651 /* proc.cpp */; };
D08A32A017B4463B00F3A533 /* reader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855813B3ACEE0099B651 /* reader.cpp */; };
D08A32A117B4463B00F3A533 /* sanity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855913B3ACEE0099B651 /* sanity.cpp */; };
D08A32A217B4463B00F3A533 /* tokenizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855D13B3ACEE0099B651 /* tokenizer.cpp */; };
D08A32A317B4463B00F3A533 /* wgetopt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855F13B3ACEE0099B651 /* wgetopt.cpp */; };
D08A32A417B4463B00F3A533 /* wildcard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0856013B3ACEE0099B651 /* wildcard.cpp */; };
D08A32A517B4463B00F3A533 /* wutil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0856113B3ACEE0099B651 /* wutil.cpp */; };
D08A32A617B4464300F3A533 /* input.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854A13B3ACEE0099B651 /* input.cpp */; };
D08A32A717B446A300F3A533 /* autoload.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */; };
D08A32A817B446A300F3A533 /* builtin_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0F3373A1506DE3C00ECEFC0 /* builtin_test.cpp */; };
D08A32A917B446A300F3A533 /* color.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0B6B0FE14E88BA400AD6C10 /* color.cpp */; };
D08A32AA17B446A300F3A533 /* common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853613B3ACEE0099B651 /* common.cpp */; };
D08A32AB17B446A300F3A533 /* env_universal_common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853813B3ACEE0099B651 /* env_universal_common.cpp */; };
D08A32AC17B446A300F3A533 /* env_universal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853913B3ACEE0099B651 /* env_universal.cpp */; };
D08A32AD17B446A300F3A533 /* event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853B13B3ACEE0099B651 /* event.cpp */; };
D08A32AE17B446A300F3A533 /* input_common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854913B3ACEE0099B651 /* input_common.cpp */; };
D08A32AF17B446A300F3A533 /* intern.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854B13B3ACEE0099B651 /* intern.cpp */; };
D08A32B017B446A300F3A533 /* io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854C13B3ACEE0099B651 /* io.cpp */; };
D08A32B117B446A300F3A533 /* iothread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0854D13B3ACEE0099B651 /* iothread.cpp */; };
D08A32B217B446A300F3A533 /* output.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855113B3ACEE0099B651 /* output.cpp */; };
D08A32B317B446A300F3A533 /* parse_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855213B3ACEE0099B651 /* parse_util.cpp */; };
D08A32B417B446A300F3A533 /* parser_keywords.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855313B3ACEE0099B651 /* parser_keywords.cpp */; };
D08A32B517B446A300F3A533 /* path.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855513B3ACEE0099B651 /* path.cpp */; };
D08A32B617B446A300F3A533 /* postfork.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D09B1C1914FC7B5B00F91077 /* postfork.cpp */; };
D08A32B717B446A300F3A533 /* screen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855A13B3ACEE0099B651 /* screen.cpp */; };
D08A32B817B446A300F3A533 /* signal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855C13B3ACEE0099B651 /* signal.cpp */; };
D08A32B917B446B100F3A533 /* parse_productions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0FE8EE7179FB75F008C9F21 /* parse_productions.cpp */; };
D08A32BA17B446B100F3A533 /* parse_tree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C52F351765284C00BFAB82 /* parse_tree.cpp */; };
D08A32BC17B4473B00F3A533 /* libncurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D02A8C15983CFA008E62BD /* libncurses.dylib */; };
D08A32BD17B4474000F3A533 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D02A8A15983CDF008E62BD /* libiconv.dylib */; };
D0A564FE168D23D800AF6161 /* man in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0A564F1168D0BAB00AF6161 /* man */; };
D0A56501168D258300AF6161 /* man in Copy Files */ = {isa = PBXBuildFile; fileRef = D0A564F1168D0BAB00AF6161 /* man */; };
D0C52F371765284C00BFAB82 /* parse_tree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0C52F351765284C00BFAB82 /* parse_tree.cpp */; };
D0CBD587159EF0E10024809C /* launch_fish.scpt in Resources */ = {isa = PBXBuildFile; fileRef = D0CBD586159EF0E10024809C /* launch_fish.scpt */; };
D0D02A67159837AD008E62BD /* complete.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853713B3ACEE0099B651 /* complete.cpp */; };
D0D02A69159837B2008E62BD /* env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853A13B3ACEE0099B651 /* env.cpp */; };
@ -150,6 +193,7 @@
D0F019FD15A977CA0034B3B1 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0C4FD9415A7D7EE00212EF1 /* config.fish */; };
D0F01A0315A978910034B3B1 /* osx_fish_launcher.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D02AFA159871B2008E62BD /* osx_fish_launcher.m */; };
D0F01A0515A978A10034B3B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0CBD583159EEE010024809C /* Foundation.framework */; };
D0FE8EE8179FB760008C9F21 /* parse_productions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0FE8EE7179FB75F008C9F21 /* parse_productions.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -292,6 +336,15 @@
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 1;
};
D08A328B17B4455100F3A533 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
D0F019F015A977010034B3B1 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@ -334,9 +387,13 @@
D025C02915D1FEA100B9DB63 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = share/tools; sourceTree = "<group>"; };
D031890915E36D9800D9CC39 /* base */ = {isa = PBXFileReference; lastKnownFileType = text; path = base; sourceTree = BUILT_PRODUCTS_DIR; };
D03EE83814DF88B200FC7150 /* lru.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lru.h; sourceTree = "<group>"; };
D052D8091868F7FC003ABCBD /* parse_execution.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parse_execution.cpp; sourceTree = "<group>"; };
D052D80A1868F7FC003ABCBD /* parse_execution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parse_execution.h; sourceTree = "<group>"; };
D07B247215BCC15700D4ADB4 /* add-shell */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "add-shell"; path = "build_tools/osx_package_scripts/add-shell"; sourceTree = "<group>"; };
D07B247515BCC4BE00D4ADB4 /* install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = install.sh; path = osx/install.sh; sourceTree = "<group>"; };
D0879AC616BF9A1A00E98E56 /* fish_term_icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = fish_term_icon.icns; path = osx/fish_term_icon.icns; sourceTree = "<group>"; };
D08A328D17B4455100F3A533 /* fish_tests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish_tests; sourceTree = BUILT_PRODUCTS_DIR; };
D08A329317B4458D00F3A533 /* fish_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fish_tests.cpp; sourceTree = "<group>"; };
D09B1C1914FC7B5B00F91077 /* postfork.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = postfork.cpp; sourceTree = "<group>"; };
D09B1C1A14FC7B5B00F91077 /* postfork.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = postfork.h; sourceTree = "<group>"; };
D0A0850313B3ACEE0099B651 /* builtin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin.h; sourceTree = "<group>"; };
@ -441,6 +498,8 @@
D0B6B0FE14E88BA400AD6C10 /* color.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = color.cpp; sourceTree = "<group>"; };
D0B6B0FF14E88BA400AD6C10 /* color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = color.h; sourceTree = "<group>"; };
D0C4FD9415A7D7EE00212EF1 /* config.fish */ = {isa = PBXFileReference; lastKnownFileType = text; name = config.fish; path = etc/config.fish; sourceTree = "<group>"; };
D0C52F351765284C00BFAB82 /* parse_tree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parse_tree.cpp; sourceTree = "<group>"; };
D0C52F361765284C00BFAB82 /* parse_tree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parse_tree.h; sourceTree = "<group>"; };
D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = autoload.cpp; sourceTree = "<group>"; };
D0C6FCCB14CFA4B7004CE8AD /* autoload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = autoload.h; sourceTree = "<group>"; };
D0C861EA16CC7054003B5A04 /* builtin_set_color.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = builtin_set_color.cpp; sourceTree = "<group>"; };
@ -457,11 +516,23 @@
D0D02AE415986537008E62BD /* fish_pager */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish_pager; sourceTree = BUILT_PRODUCTS_DIR; };
D0D02AFA159871B2008E62BD /* osx_fish_launcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = osx_fish_launcher.m; path = osx/osx_fish_launcher.m; sourceTree = "<group>"; };
D0D2693C159835CA005D9B9C /* fish */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish; sourceTree = BUILT_PRODUCTS_DIR; };
D0D9B2B318555D92001AE279 /* parse_constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = parse_constants.h; sourceTree = "<group>"; };
D0F3373A1506DE3C00ECEFC0 /* builtin_test.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = builtin_test.cpp; sourceTree = "<group>"; };
D0F5E28415A7A32D00315DFF /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
D0FE8EE6179CA8A5008C9F21 /* parse_productions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parse_productions.h; sourceTree = "<group>"; };
D0FE8EE7179FB75F008C9F21 /* parse_productions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parse_productions.cpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D08A328A17B4455100F3A533 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D08A32BD17B4474000F3A533 /* libiconv.dylib in Frameworks */,
D08A32BC17B4473B00F3A533 /* libncurses.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D0D02AB915985EF9008E62BD /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -525,6 +596,13 @@
name = "Other Build Products";
sourceTree = "<group>";
};
D08A328E17B4455100F3A533 /* fish_tests */ = {
isa = PBXGroup;
children = (
);
path = fish_tests;
sourceTree = "<group>";
};
D0A084F013B3AC130099B651 = {
isa = PBXGroup;
children = (
@ -534,6 +612,7 @@
D0D02A8E15983D5F008E62BD /* Libraries */,
D0D02AAB15985C14008E62BD /* Resources */,
D031890A15E36DB500D9CC39 /* Other Build Products */,
D08A328E17B4455100F3A533 /* fish_tests */,
D0D2693215983562005D9B9C /* Products */,
);
sourceTree = "<group>";
@ -582,6 +661,13 @@
D0A0853C13B3ACEE0099B651 /* exec.cpp */,
D0A0850C13B3ACEE0099B651 /* expand.h */,
D0A0853D13B3ACEE0099B651 /* expand.cpp */,
D0D9B2B318555D92001AE279 /* parse_constants.h */,
D0FE8EE6179CA8A5008C9F21 /* parse_productions.h */,
D0FE8EE7179FB75F008C9F21 /* parse_productions.cpp */,
D0C52F361765284C00BFAB82 /* parse_tree.h */,
D0C52F351765284C00BFAB82 /* parse_tree.cpp */,
D052D80A1868F7FC003ABCBD /* parse_execution.h */,
D052D8091868F7FC003ABCBD /* parse_execution.cpp */,
D0A0850D13B3ACEE0099B651 /* fallback.h */,
D0A0853E13B3ACEE0099B651 /* fallback.cpp */,
D0A0850E13B3ACEE0099B651 /* function.h */,
@ -657,6 +743,7 @@
D0A0856613B3ACEE0099B651 /* xdgmimemagic.cpp */,
D0A0852F13B3ACEE0099B651 /* xdgmimeparent.h */,
D0A0856713B3ACEE0099B651 /* xdgmimeparent.cpp */,
D08A329317B4458D00F3A533 /* fish_tests.cpp */,
);
name = Sources;
sourceTree = "<group>";
@ -698,6 +785,7 @@
D0D02ABC15985EF9008E62BD /* fishd */,
D0D02AD01598642A008E62BD /* fish_indent */,
D0D02AE415986537008E62BD /* fish_pager */,
D08A328D17B4455100F3A533 /* fish_tests */,
);
name = Products;
sourceTree = "<group>";
@ -730,6 +818,23 @@
/* End PBXLegacyTarget section */
/* Begin PBXNativeTarget section */
D08A328C17B4455100F3A533 /* fish_tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = D08A329217B4455100F3A533 /* Build configuration list for PBXNativeTarget "fish_tests" */;
buildPhases = (
D08A328917B4455100F3A533 /* Sources */,
D08A328A17B4455100F3A533 /* Frameworks */,
D08A328B17B4455100F3A533 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = fish_tests;
productName = fish_tests;
productReference = D08A328D17B4455100F3A533 /* fish_tests */;
productType = "com.apple.product-type.tool";
};
D0D02A9915985A75008E62BD /* fish.app */ = {
isa = PBXNativeTarget;
buildConfigurationList = D0D02AA415985A75008E62BD /* Build configuration list for PBXNativeTarget "fish.app" */;
@ -839,6 +944,7 @@
D0D02ABB15985EF9008E62BD /* fishd */,
D0D02ACF1598642A008E62BD /* fish_indent */,
D0D02AE315986537008E62BD /* fish_pager */,
D08A328C17B4455100F3A533 /* fish_tests */,
D0A564E6168CFDD800AF6161 /* man_pages */,
D0A084F713B3AC130099B651 /* Makefile */,
);
@ -1019,6 +1125,52 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
D08A328917B4455100F3A533 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D08A32B917B446B100F3A533 /* parse_productions.cpp in Sources */,
D08A32BA17B446B100F3A533 /* parse_tree.cpp in Sources */,
D08A32A717B446A300F3A533 /* autoload.cpp in Sources */,
D08A32A817B446A300F3A533 /* builtin_test.cpp in Sources */,
D08A32A917B446A300F3A533 /* color.cpp in Sources */,
D08A32AA17B446A300F3A533 /* common.cpp in Sources */,
D08A32AB17B446A300F3A533 /* env_universal_common.cpp in Sources */,
D08A32AC17B446A300F3A533 /* env_universal.cpp in Sources */,
D08A32AD17B446A300F3A533 /* event.cpp in Sources */,
D08A32AE17B446A300F3A533 /* input_common.cpp in Sources */,
D08A32AF17B446A300F3A533 /* intern.cpp in Sources */,
D08A32B017B446A300F3A533 /* io.cpp in Sources */,
D08A32B117B446A300F3A533 /* iothread.cpp in Sources */,
D08A32B217B446A300F3A533 /* output.cpp in Sources */,
D08A32B317B446A300F3A533 /* parse_util.cpp in Sources */,
D08A32B417B446A300F3A533 /* parser_keywords.cpp in Sources */,
D08A32B517B446A300F3A533 /* path.cpp in Sources */,
D08A32B617B446A300F3A533 /* postfork.cpp in Sources */,
D08A32B717B446A300F3A533 /* screen.cpp in Sources */,
D08A32B817B446A300F3A533 /* signal.cpp in Sources */,
D08A32A617B4464300F3A533 /* input.cpp in Sources */,
D08A329717B4463B00F3A533 /* complete.cpp in Sources */,
D08A329817B4463B00F3A533 /* env.cpp in Sources */,
D08A329917B4463B00F3A533 /* exec.cpp in Sources */,
D08A329A17B4463B00F3A533 /* expand.cpp in Sources */,
D08A329B17B4463B00F3A533 /* highlight.cpp in Sources */,
D08A329C17B4463B00F3A533 /* history.cpp in Sources */,
D08A329D17B4463B00F3A533 /* kill.cpp in Sources */,
D08A329E17B4463B00F3A533 /* parser.cpp in Sources */,
D08A329F17B4463B00F3A533 /* proc.cpp in Sources */,
D08A32A017B4463B00F3A533 /* reader.cpp in Sources */,
D08A32A117B4463B00F3A533 /* sanity.cpp in Sources */,
D08A32A217B4463B00F3A533 /* tokenizer.cpp in Sources */,
D08A32A317B4463B00F3A533 /* wgetopt.cpp in Sources */,
D08A32A417B4463B00F3A533 /* wildcard.cpp in Sources */,
D08A32A517B4463B00F3A533 /* wutil.cpp in Sources */,
D08A329617B445FD00F3A533 /* builtin.cpp in Sources */,
D08A329417B4458D00F3A533 /* fish_tests.cpp in Sources */,
D08A329517B445C200F3A533 /* function.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D0D02AB815985EF9008E62BD /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -1075,6 +1227,7 @@
D0D02A83159839D5008E62BD /* iothread.cpp in Sources */,
D0D02A84159839D5008E62BD /* parse_util.cpp in Sources */,
D0D02A85159839D5008E62BD /* path.cpp in Sources */,
D052D80B1868F7FC003ABCBD /* parse_execution.cpp in Sources */,
D0D02A86159839D5008E62BD /* postfork.cpp in Sources */,
D0D02A87159839D5008E62BD /* screen.cpp in Sources */,
D0D02A88159839D5008E62BD /* signal.cpp in Sources */,
@ -1102,6 +1255,8 @@
D0D02A7A15983916008E62BD /* env_universal.cpp in Sources */,
D0D02A7B15983928008E62BD /* env_universal_common.cpp in Sources */,
D0D02A89159839DF008E62BD /* fish.cpp in Sources */,
D0C52F371765284C00BFAB82 /* parse_tree.cpp in Sources */,
D0FE8EE8179FB760008C9F21 /* parse_productions.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1329,6 +1484,74 @@
};
name = Release;
};
D08A328F17B4455100F3A533 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_UNINITIALIZED_AUTOS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
D08A329017B4455100F3A533 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
D08A329117B4455100F3A533 /* Release_C++11 */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = "Release_C++11";
};
D0A084F813B3AC130099B651 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1605,6 +1828,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D08A329217B4455100F3A533 /* Build configuration list for PBXNativeTarget "fish_tests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D08A328F17B4455100F3A533 /* Debug */,
D08A329017B4455100F3A533 /* Release */,
D08A329117B4455100F3A533 /* Release_C++11 */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D0A084F513B3AC130099B651 /* Build configuration list for PBXProject "fish" */ = {
isa = XCConfigurationList;
buildConfigurations = (

File diff suppressed because it is too large Load diff

View file

@ -186,10 +186,15 @@ void function_add(const function_data_t &data, const parser_t &parser)
/* Remove the old function */
function_remove(data.name);
/* Create and store a new function */
const wchar_t *filename = reader_current_filename();
int def_offset = parser.line_number_of_character_at_offset(parser.current_block()->tok_pos) - 1;
int def_offset = -1;
if (parser.current_block() != NULL)
{
def_offset = parser.line_number_of_character_at_offset(parser.current_block()->tok_pos);
}
const function_map_t::value_type new_pair(data.name, function_info_t(data, filename, def_offset, is_autoload));
loaded_functions.insert(new_pair);

View file

@ -39,7 +39,7 @@ struct function_data_t
/**
Function definition
*/
wchar_t *definition;
const wchar_t *definition;
/**
List of all event handlers for this function
*/

File diff suppressed because it is too large Load diff

View file

@ -84,6 +84,7 @@ struct file_detection_context_t;
\param error a list in which a description of each error will be inserted. May be 0, in whcich case no error descriptions will be generated.
*/
void highlight_shell(const wcstring &buffstr, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
void highlight_shell_new_parser(const wcstring &buffstr, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
/**
Perform syntax highlighting for the text in buff. Matching quotes and paranthesis are highlighted. The result is
@ -133,5 +134,9 @@ enum
typedef unsigned int path_flags_t;
bool is_potential_path(const wcstring &const_path, const wcstring_list_t &directories, path_flags_t flags, wcstring *out_path = NULL);
/* For testing */
void highlight_shell_classic(const wcstring &buff, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
void highlight_shell_new_parser(const wcstring &buff, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
#endif

View file

@ -61,6 +61,7 @@ public:
{
return contents;
}
bool empty() const
{
return contents.empty();

259
parse_constants.h Normal file
View file

@ -0,0 +1,259 @@
/**\file parse_constants.h
Constants used in the programmatic representation of fish code.
*/
#ifndef fish_parse_constants_h
#define fish_parse_constants_h
#define PARSE_ASSERT(a) assert(a)
#define PARSER_DIE() do { fprintf(stderr, "Parser dying!\n"); exit_without_destructors(-1); } while (0)
enum parse_token_type_t
{
token_type_invalid,
// Non-terminal tokens
symbol_job_list,
symbol_job,
symbol_job_continuation,
symbol_statement,
symbol_block_statement,
symbol_block_header,
symbol_for_header,
symbol_while_header,
symbol_begin_header,
symbol_function_header,
symbol_if_statement,
symbol_if_clause,
symbol_else_clause,
symbol_else_continuation,
symbol_switch_statement,
symbol_case_item_list,
symbol_case_item,
symbol_boolean_statement,
symbol_decorated_statement,
symbol_plain_statement,
symbol_arguments_or_redirections_list,
symbol_argument_or_redirection,
symbol_argument_list,
symbol_argument,
symbol_redirection,
symbol_optional_background,
symbol_end_command,
// Terminal types
parse_token_type_string,
parse_token_type_pipe,
parse_token_type_redirection,
parse_token_type_background,
parse_token_type_end,
// Special terminal type that means no more tokens forthcoming
parse_token_type_terminate,
// Very special terminal types that don't appear in the production list
parse_special_type_parse_error,
parse_special_type_tokenizer_error,
parse_special_type_comment,
FIRST_TERMINAL_TYPE = parse_token_type_string,
LAST_TERMINAL_TYPE = parse_token_type_terminate,
LAST_TOKEN_OR_SYMBOL = parse_token_type_terminate,
FIRST_PARSE_TOKEN_TYPE = parse_token_type_string
};
enum parse_keyword_t
{
parse_keyword_none,
parse_keyword_if,
parse_keyword_else,
parse_keyword_for,
parse_keyword_in,
parse_keyword_while,
parse_keyword_begin,
parse_keyword_function,
parse_keyword_switch,
parse_keyword_case,
parse_keyword_end,
parse_keyword_and,
parse_keyword_or,
parse_keyword_not,
parse_keyword_command,
parse_keyword_builtin,
LAST_KEYWORD = parse_keyword_builtin
};
/* Statement decorations. This matches the order of productions in decorated_statement */
enum parse_statement_decoration_t
{
parse_statement_decoration_none,
parse_statement_decoration_command,
parse_statement_decoration_builtin
};
/* Parse error code list */
enum parse_error_code_t
{
parse_error_none,
/* Matching values from enum parser_error */
parse_error_syntax,
parse_error_eval,
parse_error_cmdsubst,
parse_error_generic, // unclassified error types
parse_error_tokenizer, //tokenizer error
parse_error_unbalancing_end, //end outside of block
parse_error_unbalancing_else, //else outside of if
parse_error_unbalancing_case, //case outside of switch
parse_error_double_pipe, // foo || bar, has special error message
parse_error_double_background // foo && bar, has special error message
};
enum {
PARSER_TEST_ERROR = 1,
PARSER_TEST_INCOMPLETE = 2
};
typedef unsigned int parser_test_error_bits_t;
/** Maximum number of function calls. */
#define FISH_MAX_STACK_DEPTH 128
/** 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 a '&'. 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 in redirection '%ls'")
/** Error message for wildcards with no matches */
#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" )
/** Error when using continue outside of loop */
#define INVALID_CONTINUE_ERR_MSG _( L"continue 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 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'.")
/**
While block description
*/
#define WHILE_BLOCK N_( L"'while' block" )
/**
For block description
*/
#define FOR_BLOCK N_( L"'for' block" )
/**
Breakpoint block
*/
#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
*/
#define FUNCTION_CALL_BLOCK N_( L"function invocation block" )
/**
Function invocation block description
*/
#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
*/
#define SOURCE_BLOCK N_( L"Block created by the . builtin" )
/**
Source block description
*/
#define EVENT_BLOCK N_( L"event handler block" )
/**
Unknown block description
*/
#define UNKNOWN_BLOCK N_( L"unknown/invalid block" )
#endif

1476
parse_execution.cpp Normal file

File diff suppressed because it is too large Load diff

114
parse_execution.h Normal file
View file

@ -0,0 +1,114 @@
/**\file parse_execution.h
Provides the "linkage" between a parse_node_tree_t and actual execution structures (job_t, etc.).
*/
#ifndef FISH_PARSE_EXECUTION_H
#define FISH_PARSE_EXECUTION_H
#include "config.h"
#include "util.h"
#include "parse_tree.h"
#include "proc.h"
class job_t;
struct profile_item_t;
struct block_t;
enum parse_execution_result_t
{
/* The job was successfully executed (though it have failed on its own). */
parse_execution_success,
/* The job did not execute due to some error (e.g. failed to wildcard expand). An error will have been printed and proc_last_status will have been set. */
parse_execution_errored,
/* The job was cancelled (e.g. Ctrl-C) */
parse_execution_cancelled,
/* The job was skipped (e.g. due to a not-taken 'and' command). This is a special return allowed only from the populate functions, not the run functions. */
parse_execution_skipped
};
class parse_execution_context_t
{
private:
const parse_node_tree_t tree;
const wcstring src;
io_chain_t block_io;
parser_t * const parser;
//parse_error_list_t errors;
int eval_level;
std::vector<profile_item_t*> profile_items;
/* No copying allowed */
parse_execution_context_t(const parse_execution_context_t&);
parse_execution_context_t& operator=(const parse_execution_context_t&);
/* Should I cancel? */
bool should_cancel_execution(const block_t *block) const;
/* Ways that we can stop executing a block. These are in a sort of ascending order of importance, e.g. `exit` should trump `break` */
enum execution_cancellation_reason_t
{
execution_cancellation_none,
execution_cancellation_loop_control,
execution_cancellation_skip,
execution_cancellation_exit
};
execution_cancellation_reason_t cancellation_reason(const block_t *block) const;
/* Report an error. Always returns true. */
parse_execution_result_t report_error(const parse_node_t &node, const wchar_t *fmt, ...);
/* Wildcard error helper */
parse_execution_result_t report_unmatched_wildcard_error(const parse_node_t &unmatched_wildcard);
/* Command not found support */
void handle_command_not_found(const wcstring &cmd, const parse_node_t &statement_node, int err_code);
/* Utilities */
wcstring get_source(const parse_node_t &node) const;
const parse_node_t *get_child(const parse_node_t &parent, node_offset_t which, parse_token_type_t expected_type = token_type_invalid) const;
node_offset_t get_offset(const parse_node_t &node) const;
const parse_node_t *infinite_recursive_statement_in_job_list(const parse_node_t &job_list, wcstring *out_func_name) const;
/* Indicates whether a job is a simple block (one block, no redirections) */
bool job_is_simple_block(const parse_node_t &node) const;
enum process_type_t process_type_for_command(const parse_node_t &plain_statement, const wcstring &cmd) const;
/* These create process_t structures from statements */
parse_execution_result_t populate_job_process(job_t *job, process_t *proc, const parse_node_t &statement_node);
parse_execution_result_t populate_boolean_process(job_t *job, process_t *proc, const parse_node_t &bool_statement);
parse_execution_result_t populate_plain_process(job_t *job, process_t *proc, const parse_node_t &statement);
parse_execution_result_t populate_block_process(job_t *job, process_t *proc, const parse_node_t &statement_node);
/* These encapsulate the actual logic of various (block) statements. */
parse_execution_result_t run_block_statement(const parse_node_t &statement);
parse_execution_result_t run_for_statement(const parse_node_t &header, const parse_node_t &contents);
parse_execution_result_t run_if_statement(const parse_node_t &statement);
parse_execution_result_t run_switch_statement(const parse_node_t &statement);
parse_execution_result_t run_while_statement(const parse_node_t &header, const parse_node_t &contents);
parse_execution_result_t run_function_statement(const parse_node_t &header, const parse_node_t &contents);
parse_execution_result_t run_begin_statement(const parse_node_t &header, const parse_node_t &contents);
wcstring_list_t determine_arguments(const parse_node_t &parent, const parse_node_t **out_unmatched_wildcard_node);
/* Determines the IO chain. Returns true on success, false on error */
bool determine_io_chain(const parse_node_t &statement, io_chain_t *out_chain);
parse_execution_result_t run_1_job(const parse_node_t &job_node, const block_t *associated_block);
parse_execution_result_t run_job_list(const parse_node_t &job_list_node, const block_t *associated_block);
parse_execution_result_t populate_job_from_job_node(job_t *j, const parse_node_t &job_node, const block_t *associated_block);
public:
parse_execution_context_t(const parse_node_tree_t &t, const wcstring &s, parser_t *p);
/* Start executing at the given node offset. Returns 0 if there was no error, 1 if there was an error */
parse_execution_result_t eval_node_at_offset(node_offset_t offset, const block_t *associated_block, const io_chain_t &io);
};
#endif

544
parse_productions.cpp Normal file
View file

@ -0,0 +1,544 @@
#include "parse_productions.h"
using namespace parse_productions;
#define NO_PRODUCTION ((production_option_idx_t)(-1))
static bool production_is_empty(const production_t production)
{
return production[0] == token_type_invalid;
}
/* Empty productions are allowed but must be first. Validate that the given production is in the valid range, i.e. it is either not empty or there is a non-empty production after it */
static bool production_is_valid(const production_options_t production_list, production_option_idx_t which)
{
if (which < 0 || which >= MAX_PRODUCTIONS)
return false;
bool nonempty_found = false;
for (int i=which; i < MAX_PRODUCTIONS; i++)
{
if (! production_is_empty(production_list[i]))
{
nonempty_found = true;
break;
}
}
return nonempty_found;
}
#define PRODUCTIONS(sym) static const production_options_t productions_##sym
#define RESOLVE(sym) static production_option_idx_t resolve_##sym (const parse_token_t &token1, const parse_token_t &token2)
#define RESOLVE_ONLY(sym) static production_option_idx_t resolve_##sym (const parse_token_t &input1, const parse_token_t &input2) { return 0; }
#define KEYWORD(x) ((x) + LAST_TOKEN_OR_SYMBOL + 1)
/* A job_list is a list of jobs, separated by semicolons or newlines */
PRODUCTIONS(job_list) =
{
{},
{symbol_job, symbol_job_list},
{parse_token_type_end, symbol_job_list}
};
RESOLVE(job_list)
{
switch (token1.type)
{
case parse_token_type_string:
// some keywords are special
switch (token1.keyword)
{
case parse_keyword_end:
case parse_keyword_else:
case parse_keyword_case:
// End this job list
return 0;
default:
// Normal string
return 1;
}
case parse_token_type_pipe:
case parse_token_type_redirection:
case parse_token_type_background:
return 1;
case parse_token_type_end:
// Empty line
return 2;
case parse_token_type_terminate:
// no more commands, just transition to empty
return 0;
default:
return NO_PRODUCTION;
}
}
/* A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases like if statements, where we require a command). To represent "non-empty", we require a statement, followed by a possibly empty job_continuation */
PRODUCTIONS(job) =
{
{symbol_statement, symbol_job_continuation}
};
RESOLVE_ONLY(job)
PRODUCTIONS(job_continuation) =
{
{},
{parse_token_type_pipe, symbol_statement, symbol_job_continuation}
};
RESOLVE(job_continuation)
{
switch (token1.type)
{
case parse_token_type_pipe:
// Pipe, continuation
return 1;
default:
// Not a pipe, no job continuation
return 0;
}
}
/* A statement is a normal command, or an if / while / and etc */
PRODUCTIONS(statement) =
{
{symbol_boolean_statement},
{symbol_block_statement},
{symbol_if_statement},
{symbol_switch_statement},
{symbol_decorated_statement}
};
RESOLVE(statement)
{
// Go to decorated statements if the subsequent token looks like '--'
// If we are 'begin', then we expect to be invoked with no arguments. But if we are anything else, we require an argument, so do the same thing if the subsequent token is a line end.
if (token1.type == parse_token_type_string)
{
// If the next token looks like an option (starts with a dash), then parse it as a decorated statement
if (token2.has_dash_prefix)
{
return 4;
}
// Likewise if the next token doesn't look like an argument at all. This corresponds to e.g. a "naked if".
bool naked_invocation_invokes_help = (token1.keyword != parse_keyword_begin && token1.keyword != parse_keyword_end);
if (naked_invocation_invokes_help && (token2.type == parse_token_type_end || token2.type == parse_token_type_terminate))
{
return 4;
}
}
switch (token1.type)
{
case parse_token_type_string:
switch (token1.keyword)
{
case parse_keyword_and:
case parse_keyword_or:
case parse_keyword_not:
return 0;
case parse_keyword_for:
case parse_keyword_while:
case parse_keyword_function:
case parse_keyword_begin:
return 1;
case parse_keyword_if:
return 2;
case parse_keyword_else:
return NO_PRODUCTION;
case parse_keyword_switch:
return 3;
case parse_keyword_end:
return NO_PRODUCTION;
// 'in' is only special within a for_header
case parse_keyword_in:
case parse_keyword_none:
case parse_keyword_command:
case parse_keyword_builtin:
case parse_keyword_case:
return 4;
}
break;
case parse_token_type_pipe:
case parse_token_type_redirection:
case parse_token_type_background:
case parse_token_type_terminate:
return NO_PRODUCTION;
//parse_error(L"statement", token);
default:
return NO_PRODUCTION;
}
}
PRODUCTIONS(if_statement) =
{
{symbol_if_clause, symbol_else_clause, symbol_end_command, symbol_arguments_or_redirections_list}
};
RESOLVE_ONLY(if_statement)
PRODUCTIONS(if_clause) =
{
{ KEYWORD(parse_keyword_if), symbol_job, parse_token_type_end, symbol_job_list }
};
RESOLVE_ONLY(if_clause)
PRODUCTIONS(else_clause) =
{
{ },
{ KEYWORD(parse_keyword_else), symbol_else_continuation }
};
RESOLVE(else_clause)
{
switch (token1.keyword)
{
case parse_keyword_else:
return 1;
default:
return 0;
}
}
PRODUCTIONS(else_continuation) =
{
{symbol_if_clause, symbol_else_clause},
{parse_token_type_end, symbol_job_list}
};
RESOLVE(else_continuation)
{
switch (token1.keyword)
{
case parse_keyword_if:
return 0;
default:
return 1;
}
}
PRODUCTIONS(switch_statement) =
{
{ KEYWORD(parse_keyword_switch), parse_token_type_string, parse_token_type_end, symbol_case_item_list, symbol_end_command, symbol_arguments_or_redirections_list}
};
RESOLVE_ONLY(switch_statement)
PRODUCTIONS(case_item_list) =
{
{},
{symbol_case_item, symbol_case_item_list},
{parse_token_type_end, symbol_case_item_list}
};
RESOLVE(case_item_list)
{
if (token1.keyword == parse_keyword_case) return 1;
else if (token1.type == parse_token_type_end) return 2; //empty line
else return 0;
}
PRODUCTIONS(case_item) =
{
{KEYWORD(parse_keyword_case), symbol_argument_list, parse_token_type_end, symbol_job_list}
};
RESOLVE_ONLY(case_item)
PRODUCTIONS(argument_list) =
{
{},
{symbol_argument, symbol_argument_list}
};
RESOLVE(argument_list)
{
switch (token1.type)
{
case parse_token_type_string:
return 1;
default:
return 0;
}
}
PRODUCTIONS(block_statement) =
{
{symbol_block_header, parse_token_type_end, symbol_job_list, symbol_end_command, symbol_arguments_or_redirections_list}
};
RESOLVE_ONLY(block_statement)
PRODUCTIONS(block_header) =
{
{symbol_for_header},
{symbol_while_header},
{symbol_function_header},
{symbol_begin_header}
};
RESOLVE(block_header)
{
switch (token1.keyword)
{
case parse_keyword_for:
return 0;
case parse_keyword_while:
return 1;
case parse_keyword_function:
return 2;
case parse_keyword_begin:
return 3;
default:
return NO_PRODUCTION;
}
}
PRODUCTIONS(for_header) =
{
{KEYWORD(parse_keyword_for), parse_token_type_string, KEYWORD(parse_keyword_in), symbol_argument_list}
};
RESOLVE_ONLY(for_header)
PRODUCTIONS(while_header) =
{
{KEYWORD(parse_keyword_while), symbol_job}
};
RESOLVE_ONLY(while_header)
PRODUCTIONS(begin_header) =
{
{KEYWORD(parse_keyword_begin)}
};
RESOLVE_ONLY(begin_header)
PRODUCTIONS(function_header) =
{
{KEYWORD(parse_keyword_function), symbol_argument, symbol_argument_list}
};
RESOLVE_ONLY(function_header)
/* A boolean statement is AND or OR or NOT */
PRODUCTIONS(boolean_statement) =
{
{KEYWORD(parse_keyword_and), symbol_statement},
{KEYWORD(parse_keyword_or), symbol_statement},
{KEYWORD(parse_keyword_not), symbol_statement}
};
RESOLVE(boolean_statement)
{
switch (token1.keyword)
{
case parse_keyword_and:
return 0;
case parse_keyword_or:
return 1;
case parse_keyword_not:
return 2;
default:
return NO_PRODUCTION;
}
}
PRODUCTIONS(decorated_statement) =
{
{symbol_plain_statement},
{KEYWORD(parse_keyword_command), symbol_plain_statement},
{KEYWORD(parse_keyword_builtin), symbol_plain_statement},
};
RESOLVE(decorated_statement)
{
/* If this is e.g. 'command --help' then the command is 'command' and not a decoration. If the second token is not a string, then this is a naked 'command' and we should execute it as undecorated. */
if (token2.type != parse_token_type_string || token2.has_dash_prefix)
{
return 0;
}
switch (token1.keyword)
{
default:
return 0;
case parse_keyword_command:
return 1;
case parse_keyword_builtin:
return 2;
}
}
PRODUCTIONS(plain_statement) =
{
{parse_token_type_string, symbol_arguments_or_redirections_list, symbol_optional_background}
};
RESOLVE_ONLY(plain_statement)
PRODUCTIONS(arguments_or_redirections_list) =
{
{},
{symbol_argument_or_redirection, symbol_arguments_or_redirections_list}
};
RESOLVE(arguments_or_redirections_list)
{
switch (token1.type)
{
case parse_token_type_string:
case parse_token_type_redirection:
return 1;
default:
return 0;
}
}
PRODUCTIONS(argument_or_redirection) =
{
{symbol_argument},
{symbol_redirection}
};
RESOLVE(argument_or_redirection)
{
switch (token1.type)
{
case parse_token_type_string:
return 0;
case parse_token_type_redirection:
return 1;
default:
return NO_PRODUCTION;
}
}
PRODUCTIONS(argument) =
{
{parse_token_type_string}
};
RESOLVE_ONLY(argument)
PRODUCTIONS(redirection) =
{
{parse_token_type_redirection, parse_token_type_string}
};
RESOLVE_ONLY(redirection)
PRODUCTIONS(optional_background) =
{
{},
{ parse_token_type_background }
};
RESOLVE(optional_background)
{
switch (token1.type)
{
case parse_token_type_background:
return 1;
default:
return 0;
}
}
PRODUCTIONS(end_command) =
{
{KEYWORD(parse_keyword_end)}
};
RESOLVE_ONLY(end_command)
#define TEST(sym) case (symbol_##sym): production_list = & productions_ ## sym ; resolver = resolve_ ## sym ; break;
const production_t *parse_productions::production_for_token(parse_token_type_t node_type, const parse_token_t &input1, const parse_token_t &input2, production_option_idx_t *out_which_production, wcstring *out_error_text)
{
bool log_it = false;
if (log_it)
{
fprintf(stderr, "Resolving production for %ls with input token <%ls>\n", token_type_description(node_type).c_str(), input1.describe().c_str());
}
/* Fetch the list of productions and the function to resolve them */
const production_options_t *production_list = NULL;
production_option_idx_t (*resolver)(const parse_token_t &input1, const parse_token_t &input2) = NULL;
switch (node_type)
{
TEST(job_list)
TEST(job)
TEST(statement)
TEST(job_continuation)
TEST(boolean_statement)
TEST(block_statement)
TEST(if_statement)
TEST(if_clause)
TEST(else_clause)
TEST(else_continuation)
TEST(switch_statement)
TEST(decorated_statement)
TEST(case_item_list)
TEST(case_item)
TEST(argument_list)
TEST(block_header)
TEST(for_header)
TEST(while_header)
TEST(begin_header)
TEST(function_header)
TEST(plain_statement)
TEST(arguments_or_redirections_list)
TEST(argument_or_redirection)
TEST(argument)
TEST(redirection)
TEST(optional_background)
TEST(end_command)
case parse_token_type_string:
case parse_token_type_pipe:
case parse_token_type_redirection:
case parse_token_type_background:
case parse_token_type_end:
case parse_token_type_terminate:
fprintf(stderr, "Terminal token type %ls passed to %s\n", token_type_description(node_type).c_str(), __FUNCTION__);
PARSER_DIE();
break;
case parse_special_type_parse_error:
case parse_special_type_tokenizer_error:
case parse_special_type_comment:
fprintf(stderr, "Special type %ls passed to %s\n", token_type_description(node_type).c_str(), __FUNCTION__);
PARSER_DIE();
break;
case token_type_invalid:
fprintf(stderr, "token_type_invalid passed to %s\n", __FUNCTION__);
PARSER_DIE();
break;
}
PARSE_ASSERT(production_list != NULL);
PARSE_ASSERT(resolver != NULL);
const production_t *result = NULL;
production_option_idx_t which = resolver(input1, input2);
if (log_it)
{
fprintf(stderr, "\tresolved to %u\n", (unsigned)which);
}
if (which == NO_PRODUCTION)
{
if (log_it)
{
fprintf(stderr, "Node type '%ls' has no production for input '%ls' (in %s)\n", token_type_description(node_type).c_str(), input1.describe().c_str(), __FUNCTION__);
}
result = NULL;
}
else
{
PARSE_ASSERT(production_is_valid(*production_list, which));
result = &((*production_list)[which]);
}
*out_which_production = which;
return result;
}

71
parse_productions.h Normal file
View file

@ -0,0 +1,71 @@
/**\file parse_tree.h
Programmatic representation of fish code.
*/
#ifndef FISH_PARSE_TREE_CONSTRUCTION_H
#define FISH_PARSE_TREE_CONSTRUCTION_H
#include "parse_tree.h"
#include <inttypes.h>
namespace parse_productions
{
#define MAX_PRODUCTIONS 5
#define MAX_SYMBOLS_PER_PRODUCTION 6
typedef uint32_t production_tag_t;
/* A production is an array of unsigned char. Symbols are encoded directly as their symbol value. Keywords are encoded with an offset of LAST_TOKEN_OR_SYMBOL + 1. So essentially we glom together keywords and symbols. */
typedef uint8_t production_element_t;
/* An index into a production option list */
typedef uint8_t production_option_idx_t;
/* A production is an array of production elements */
typedef production_element_t const production_t[MAX_SYMBOLS_PER_PRODUCTION];
/* A production options is an array of (possible) productions */
typedef production_t production_options_t[MAX_PRODUCTIONS];
/* Resolve the type from a production element */
inline parse_token_type_t production_element_type(production_element_t elem)
{
if (elem > LAST_TOKEN_OR_SYMBOL)
{
return parse_token_type_string;
}
else
{
return static_cast<parse_token_type_t>(elem);
}
}
/* Resolve the keyword from a production element */
inline parse_keyword_t production_element_keyword(production_element_t elem)
{
if (elem > LAST_TOKEN_OR_SYMBOL)
{
// First keyword is LAST_TOKEN_OR_SYMBOL + 1
return static_cast<parse_keyword_t>(elem - LAST_TOKEN_OR_SYMBOL - 1);
}
else
{
return parse_keyword_none;
}
}
/* Check if an element is valid */
inline bool production_element_is_valid(production_element_t elem)
{
return elem != token_type_invalid;
}
/* Fetch a production. We are passed two input tokens. The first input token is guaranteed to not be invalid; the second token may be invalid if there's no more tokens. */
const production_t *production_for_token(parse_token_type_t node_type, const parse_token_t &input1, const parse_token_t &input2, production_option_idx_t *out_which_production, wcstring *out_error_text);
}
#endif

1492
parse_tree.cpp Normal file

File diff suppressed because it is too large Load diff

271
parse_tree.h Normal file
View file

@ -0,0 +1,271 @@
/**\file parse_tree.h
Programmatic representation of fish code.
*/
#ifndef FISH_PARSE_PRODUCTIONS_H
#define FISH_PARSE_PRODUCTIONS_H
#include <wchar.h>
#include "config.h"
#include "util.h"
#include "common.h"
#include "tokenizer.h"
#include "parse_constants.h"
#include <vector>
#include <inttypes.h>
class parse_node_t;
class parse_node_tree_t;
typedef size_t node_offset_t;
#define NODE_OFFSET_INVALID (static_cast<node_offset_t>(-1))
struct parse_error_t
{
/** Text of the error */
wcstring text;
/** Code for the error */
enum parse_error_code_t code;
/** Offset and length of the token in the source code that triggered this error */
size_t source_start;
size_t source_length;
/** Return a string describing the error, suitable for presentation to the user. If skip_caret is false, the offending line with a caret is printed as well */
wcstring describe(const wcstring &src, bool skip_caret = false) const;
};
typedef std::vector<parse_error_t> parse_error_list_t;
/* Returns a description of a list of parse errors */
wcstring parse_errors_description(const parse_error_list_t &errors, const wcstring &src, const wchar_t *prefix = NULL);
/** A struct representing the token type that we use internally */
struct parse_token_t
{
enum parse_token_type_t type; // The type of the token as represented by the parser
enum parse_keyword_t keyword; // Any keyword represented by this token
bool has_dash_prefix; // Hackish: whether the source contains a dash prefix
size_t source_start;
size_t source_length;
wcstring describe() const;
wcstring user_presentable_description() const;
};
enum
{
parse_flag_none = 0,
/* Attempt to build a "parse tree" no matter what. This may result in a 'forest' of disconnected trees. This is intended to be used by syntax highlighting. */
parse_flag_continue_after_error = 1 << 0,
/* Include comment tokens */
parse_flag_include_comments = 1 << 1,
/* Indicate that the tokenizer should accept incomplete tokens */
parse_flag_accept_incomplete_tokens = 1 << 2,
/* Indicate that the parser should not generate the terminate token, allowing an 'unfinished' tree where some nodes may have no productions. */
parse_flag_leave_unterminated = 1 << 3
};
typedef unsigned int parse_tree_flags_t;
wcstring parse_dump_tree(const parse_node_tree_t &tree, const wcstring &src);
wcstring token_type_description(parse_token_type_t type);
wcstring keyword_description(parse_keyword_t type);
/** Class for nodes of a parse tree */
class parse_node_t
{
public:
/* Type of the node */
enum parse_token_type_t type;
/* Start in the source code */
size_t source_start;
/* Length of our range in the source code */
size_t source_length;
/* Parent */
node_offset_t parent;
/* Children */
node_offset_t child_start;
uint8_t child_count;
/* Which production was used */
uint8_t production_idx;
/* Description */
wcstring describe(void) const;
/* Constructor */
explicit parse_node_t(parse_token_type_t ty) : type(ty), source_start(-1), source_length(0), parent(NODE_OFFSET_INVALID), child_start(0), child_count(0), production_idx(-1)
{
}
node_offset_t child_offset(node_offset_t which) const
{
PARSE_ASSERT(which < child_count);
return child_start + which;
}
/* Indicate if this node has a range of source code associated with it */
bool has_source() const
{
return source_start != (size_t)(-1);
}
/* Gets source for the node, or the empty string if it has no source */
wcstring get_source(const wcstring &str) const
{
if (! has_source())
return wcstring();
else
return wcstring(str, this->source_start, this->source_length);
}
/* Returns whether the given location is within the source range or at its end */
bool location_in_or_at_end_of_source_range(size_t loc) const
{
return has_source() && source_start <= loc && loc - source_start <= source_length;
}
};
/* The parse tree itself */
class parse_node_tree_t : public std::vector<parse_node_t>
{
public:
/* Get the node corresponding to a child of the given node, or NULL if there is no such child. If expected_type is provided, assert that the node has that type.
*/
const parse_node_t *get_child(const parse_node_t &parent, node_offset_t which, parse_token_type_t expected_type = token_type_invalid) const;
/* Find the first direct child of the given node of the given type. asserts on failure
*/
const parse_node_t &find_child(const parse_node_t &parent, parse_token_type_t type) const;
/* Get the node corresponding to the parent of the given node, or NULL if there is no such child. If expected_type is provided, only returns the parent if it is of that type. Note the asymmetry: get_child asserts since the children are known, but get_parent does not, since the parent may not be known. */
const parse_node_t *get_parent(const parse_node_t &node, parse_token_type_t expected_type = token_type_invalid) const;
/* Returns the first ancestor of the given type, or NULL. */
const parse_node_t *get_first_ancestor_of_type(const parse_node_t &node, parse_token_type_t desired_type) const;
/* Find all the nodes of a given type underneath a given node, up to max_count of them */
typedef std::vector<const parse_node_t *> parse_node_list_t;
parse_node_list_t find_nodes(const parse_node_t &parent, parse_token_type_t type, size_t max_count = (size_t)(-1)) const;
/* Finds the last node of a given type underneath a given node, or NULL if it could not be found. If parent is NULL, this finds the last node in the tree of that type. */
const parse_node_t *find_last_node_of_type(parse_token_type_t type, const parse_node_t *parent = NULL) const;
/* Finds a node containing the given source location. If 'parent' is not NULL, it must be an ancestor. */
const parse_node_t *find_node_matching_source_location(parse_token_type_t type, size_t source_loc, const parse_node_t *parent) const;
/* Indicate if the given argument_list or arguments_or_redirections_list is a root list, or has a parent */
bool argument_list_is_root(const parse_node_t &node) const;
/* Utilities */
/* Given a plain statement, get the decoration (from the parent node), or none if there is no decoration */
enum parse_statement_decoration_t decoration_for_plain_statement(const parse_node_t &node) const;
/* Given a plain statement, get the command by reference (from the child node). Returns true if successful. Clears the command on failure. */
bool command_for_plain_statement(const parse_node_t &node, const wcstring &src, wcstring *out_cmd) const;
/* Given a plain statement, return true if the statement is part of a pipeline. If include_first is set, the first command in a pipeline is considered part of it; otherwise only the second or additional commands are */
bool statement_is_in_pipeline(const parse_node_t &node, bool include_first) const;
/* Given a redirection, get the redirection type (or TOK_NONE) and target (file path, or fd) */
enum token_type type_for_redirection(const parse_node_t &node, const wcstring &src, int *out_fd, wcstring *out_target) const;
/* If the given node is a block statement, returns the header node (for_header, while_header, begin_header, or function_header). Otherwise returns NULL */
const parse_node_t *header_node_for_block_statement(const parse_node_t &node) const;
/* Given a node list (e.g. of type symbol_job_list) and a node type (e.g. symbol_job), return the next element of the given type in that list, and the tail (by reference). Returns NULL if we've exhausted the list. */
const parse_node_t *next_node_in_node_list(const parse_node_t &node_list, parse_token_type_t item_type, const parse_node_t **list_tail) const;
/* Given a job, return all of its statements. These are 'specific statements' (e.g. symbol_decorated_statement, not symbol_statement) */
parse_node_list_t specific_statements_for_job(const parse_node_t &job) const;
};
/* The big entry point. Parse a string! */
bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse_node_tree_t *output, parse_error_list_t *errors, bool log_it = false);
/* Fish grammar:
# A job_list is a list of jobs, separated by semicolons or newlines
job_list = <empty> |
job job_list
<TOK_END> job_list
# A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases like if statements, where we require a command). To represent "non-empty", we require a statement, followed by a possibly empty job_continuation
job = statement job_continuation
job_continuation = <empty> |
<TOK_PIPE> statement job_continuation
# A statement is a normal command, or an if / while / and etc
statement = boolean_statement | block_statement | if_statement | switch_statement | decorated_statement
# A block is a conditional, loop, or begin/end
if_statement = if_clause else_clause end_command arguments_or_redirections_list
if_clause = <IF> job STATEMENT_TERMINATOR job_list
else_clause = <empty> |
<ELSE> else_continuation
else_continuation = if_clause else_clause |
STATEMENT_TERMINATOR job_list
switch_statement = SWITCH <TOK_STRING> STATEMENT_TERMINATOR case_item_list end_command arguments_or_redirections_list
case_item_list = <empty> |
case_item case_item_list |
<TOK_END> case_item_list
case_item = CASE argument_list STATEMENT_TERMINATOR job_list
block_statement = block_header <TOK_END> job_list end_command arguments_or_redirections_list
block_header = for_header | while_header | function_header | begin_header
for_header = FOR var_name IN argument_list
while_header = WHILE job
begin_header = BEGIN
# Functions take arguments, and require at least one (the name). No redirections allowed.
function_header = FUNCTION argument argument_list
# A boolean statement is AND or OR or NOT
boolean_statement = AND statement | OR statement | NOT statement
# A decorated_statement is a command with a list of arguments_or_redirections, possibly with "builtin" or "command"
decorated_statement = plain_statement | COMMAND plain_statement | BUILTIN plain_statement
plain_statement = <TOK_STRING> arguments_or_redirections_list optional_background
argument_list = <empty> | argument argument_list
arguments_or_redirections_list = <empty> |
argument_or_redirection arguments_or_redirections_list
argument_or_redirection = argument | redirection
argument = <TOK_STRING>
redirection = <TOK_REDIRECTION> <TOK_STRING>
terminator = <TOK_END> | <TOK_BACKGROUND>
optional_background = <empty> | <TOK_BACKGROUND>
end_command = END
*/
#endif

View file

@ -38,18 +38,13 @@
#include "env.h"
#include "signal.h"
#include "wildcard.h"
#include "parse_tree.h"
#include "parser.h"
/**
Maximum number of autoloaded items opf a specific type to keep in
memory at a time.
Error message for improper use of the exec builtin
*/
#define AUTOLOAD_MAX 10
/**
Minimum time, in seconds, before an autoloaded item will be
unloaded
*/
#define AUTOLOAD_MIN_AGE 60
#define EXEC_ERR_MSG _(L"The '%ls' command can not be used in a pipeline")
int parse_util_lineno(const wchar_t *str, size_t offset)
{
@ -164,7 +159,7 @@ int parse_util_locate_cmdsubst(const wchar_t *in, wchar_t **begin, wchar_t **end
CHECK(in, 0);
for (pos = (wchar_t *)in; *pos; pos++)
for (pos = const_cast<wchar_t *>(in); *pos; pos++)
{
if (prev != '\\')
{
@ -240,6 +235,42 @@ int parse_util_locate_cmdsubst(const wchar_t *in, wchar_t **begin, wchar_t **end
return 1;
}
int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_offset, wcstring *out_contents, size_t *out_start, size_t *out_end, bool accept_incomplete)
{
/* Clear the return values */
out_contents->clear();
*out_start = 0;
*out_end = str.size();
/* Nothing to do if the offset is at or past the end of the string. */
if (*inout_cursor_offset >= str.size())
return 0;
/* Defer to the wonky version */
const wchar_t * const buff = str.c_str();
const wchar_t * const valid_range_start = buff + *inout_cursor_offset, *valid_range_end = buff + str.size();
wchar_t *cmdsub_begin = NULL, *cmdsub_end = NULL;
int ret = parse_util_locate_cmdsubst(valid_range_start, &cmdsub_begin, &cmdsub_end, accept_incomplete);
if (ret > 0)
{
/* The command substitutions must not be NULL and must be in the valid pointer range, and the end must be bigger than the beginning */
assert(cmdsub_begin != NULL && cmdsub_begin >= valid_range_start && cmdsub_begin <= valid_range_end);
assert(cmdsub_end != NULL && cmdsub_end > cmdsub_begin && cmdsub_end >= valid_range_start && cmdsub_end <= valid_range_end);
/* Assign the substring to the out_contents */
const wchar_t *interior_begin = cmdsub_begin + 1;
out_contents->assign(interior_begin, cmdsub_end - interior_begin);
/* Return the start and end */
*out_start = cmdsub_begin - buff;
*out_end = cmdsub_end - buff;
/* Update the inout_cursor_offset. Note this may cause it to exceed str.size(), though overflow is not likely */
*inout_cursor_offset = 1 + *out_end;
}
return ret;
}
void parse_util_cmdsubst_extent(const wchar_t *buff, size_t cursor_pos, const wchar_t **a, const wchar_t **b)
{
const wchar_t * const cursor = buff + cursor_pos;
@ -768,3 +799,335 @@ wcstring parse_util_escape_string_with_quote(const wcstring &cmd, wchar_t quote)
}
return result;
}
/* We are given a parse tree, the index of a node within the tree, its indent, and a vector of indents the same size as the original source string. Set the indent correspdonding to the node's source range, if appropriate.
trailing_indent is the indent for nodes with unrealized source, i.e. if I type 'if false <ret>' then we have an if node with an empty job list (without source) but we want the last line to be indented anyways.
switch statements also indent.
max_visited_node_idx is the largest index we visited.
*/
static void compute_indents_recursive(const parse_node_tree_t &tree, node_offset_t node_idx, int node_indent, parse_token_type_t parent_type, std::vector<int> *indents, int *trailing_indent, node_offset_t *max_visited_node_idx)
{
/* Guard against incomplete trees */
if (node_idx > tree.size())
return;
/* Update max_visited_node_idx */
if (node_idx > *max_visited_node_idx)
*max_visited_node_idx = node_idx;
/* We could implement this by utilizing the fish grammar. But there's an easy trick instead: almost everything that wraps a job list should be indented by 1. So just find all of the job lists. One exception is switch; the other exception is job_list itself: a job_list is a job and a job_list, and we want that child list to be indented the same as the parent. So just find all job_lists whose parent is not a job_list, and increment their indent by 1. */
const parse_node_t &node = tree.at(node_idx);
const parse_token_type_t node_type = node.type;
/* Increment the indent if we are either a root job_list, or root case_item_list */
const bool is_root_job_list = (node_type == symbol_job_list && parent_type != symbol_job_list);
const bool is_root_case_item_list = (node_type == symbol_case_item_list && parent_type != symbol_case_item_list);
if (is_root_job_list || is_root_case_item_list)
{
node_indent += 1;
}
/* If we have source, store the trailing indent unconditionally. If we do not have source, store the trailing indent only if ours is bigger; this prevents the trailing "run" of terminal job lists from affecting the trailing indent. For example, code like this:
if foo
will be parsed as this:
job_list
job
if_statement
job [if]
job_list [empty]
job_list [empty]
There's two "terminal" job lists, and we want the innermost one.
Note we are relying on the fact that nodes are in the same order as the source, i.e. an in-order traversal of the node tree also traverses the source from beginning to end.
*/
if (node.has_source() || node_indent > *trailing_indent)
{
*trailing_indent = node_indent;
}
/* Store the indent into the indent array */
if (node.has_source())
{
assert(node.source_start < indents->size());
indents->at(node.source_start) = node_indent;
}
/* Recursive to all our children */
for (node_offset_t idx = 0; idx < node.child_count; idx++)
{
/* Note we pass our type to our child, which becomes its parent node type */
compute_indents_recursive(tree, node.child_start + idx, node_indent, node_type, indents, trailing_indent, max_visited_node_idx);
}
}
std::vector<int> parse_util_compute_indents(const wcstring &src)
{
/* Make a vector the same size as the input string, which contains the indents. Initialize them to -1. */
const size_t src_size = src.size();
std::vector<int> indents(src_size, -1);
/* Parse the string. We pass continue_after_error to produce a forest; the trailing indent of the last node we visited becomes the input indent of the next. I.e. in the case of 'switch foo ; cas', we get an invalid parse tree (since 'cas' is not valid) but we indent it as if it were a case item list */
parse_node_tree_t tree;
parse_tree_from_string(src, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &tree, NULL /* errors */);
/* Start indenting at the first node. If we have a parse error, we'll have to start indenting from the top again */
node_offset_t start_node_idx = 0;
int last_trailing_indent = 0;
while (start_node_idx < tree.size())
{
/* The indent that we'll get for the last line */
int trailing_indent = 0;
/* Biggest offset we visited */
node_offset_t max_visited_node_idx = 0;
/* Invoke the recursive version. As a hack, pass job_list for the 'parent' token type, which will prevent the really-root job list from indenting */
compute_indents_recursive(tree, start_node_idx, last_trailing_indent, symbol_job_list, &indents, &trailing_indent, &max_visited_node_idx);
/* We may have more to indent. The trailing indent becomes our current indent. Start at the node after the last we visited. */
last_trailing_indent = trailing_indent;
start_node_idx = max_visited_node_idx + 1;
}
int last_indent = 0;
for (size_t i=0; i<src_size; i++)
{
int this_indent = indents.at(i);
if (this_indent < 0)
{
indents.at(i) = last_indent;
}
else
{
/* New indent level */
last_indent = this_indent;
/* 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", src.at(prev_char_idx)))
break;
indents.at(prev_char_idx) = last_indent;
}
}
}
/* Ensure trailing whitespace has the trailing indent. This makes sure a new line is correctly indented even if it is empty. */
size_t suffix_idx = src_size;
while (suffix_idx--)
{
if (!wcschr(L" \n\t\r", src.at(suffix_idx)))
break;
indents.at(suffix_idx) = last_trailing_indent;
}
return indents;
}
/* Append a syntax error to the given error list */
static bool append_syntax_error(parse_error_list_t *errors, const parse_node_t &node, const wchar_t *fmt, ...)
{
parse_error_t error;
error.source_start = node.source_start;
error.source_length = node.source_length;
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
*/
static int parser_is_pipe_forbidden(const wcstring &word)
{
return contains(word,
L"exec",
L"case",
L"break",
L"return",
L"continue");
}
// Check if the first argument under the given node is --help
static bool first_argument_is_help(const parse_node_tree_t &node_tree, const parse_node_t &node, const wcstring &src)
{
bool is_help = false;
const parse_node_tree_t::parse_node_list_t arg_nodes = node_tree.find_nodes(node, symbol_argument, 1);
if (! arg_nodes.empty())
{
// Check the first argument only
const parse_node_t &arg = *arg_nodes.at(0);
const wcstring first_arg_src = arg.get_source(src);
is_help = parser_t::is_help(first_arg_src.c_str(), 3);
}
return is_help;
}
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, parse_error_list_t *out_errors)
{
parse_node_tree_t node_tree;
parse_error_list_t parse_errors;
// Whether we encountered a parse error
bool errored = false;
// Whether we encountered an unclosed block
// We detect this via an 'end_command' block without source
bool has_unclosed_block = false;
// Parse the input string into a parse tree
// Some errors are detected here
bool parsed = parse_tree_from_string(buff_src, parse_flag_leave_unterminated, &node_tree, &parse_errors);
if (! parsed)
{
errored = true;
}
// Expand all commands
// Verify 'or' and 'and' not used inside pipelines
// Verify pipes via parser_is_pipe_forbidden
// Verify return only within a function
if (! errored)
{
const size_t node_tree_size = node_tree.size();
for (size_t i=0; i < node_tree_size; i++)
{
const parse_node_t &node = node_tree.at(i);
if (node.type == symbol_end_command && ! node.has_source())
{
// an 'end' without source is an unclosed block
has_unclosed_block = true;
}
else if (node.type == symbol_boolean_statement)
{
// 'or' and 'and' can be in a pipeline, as long as they're first
// These numbers 0 and 1 correspond to productions for boolean_statement. This should be cleaned up.
bool is_and = (node.production_idx == 0), is_or = (node.production_idx == 1);
if ((is_and || is_or) && node_tree.statement_is_in_pipeline(node, false /* don't count first */))
{
errored = append_syntax_error(&parse_errors, node, EXEC_ERR_MSG, is_and ? L"and" : L"or");
}
}
else if (node.type == symbol_plain_statement)
{
wcstring command;
if (node_tree.command_for_plain_statement(node, buff_src, &command))
{
// Check that we can expand the command
if (! expand_one(command, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS))
{
errored = append_syntax_error(&parse_errors, node, ILLEGAL_CMD_ERR_MSG, command.c_str());
}
// Check that pipes are sound
if (! errored && parser_is_pipe_forbidden(command))
{
// forbidden commands cannot be in a pipeline at all
if (node_tree.statement_is_in_pipeline(node, true /* count first */))
{
errored = append_syntax_error(&parse_errors, node, EXEC_ERR_MSG, command.c_str());
}
}
// Check that we don't return from outside a function
// But we allow it if it's 'return --help'
if (! errored && command == L"return")
{
const parse_node_t *ancestor = &node;
bool found_function = false;
while (ancestor != NULL)
{
const parse_node_t *possible_function_header = node_tree.header_node_for_block_statement(*ancestor);
if (possible_function_header != NULL && possible_function_header->type == symbol_function_header)
{
found_function = true;
break;
}
ancestor = node_tree.get_parent(*ancestor);
}
if (! found_function && ! first_argument_is_help(node_tree, node, buff_src))
{
errored = append_syntax_error(&parse_errors, node, INVALID_RETURN_ERR_MSG);
}
}
// Check that we don't break or continue from outside a loop
if (! errored && (command == L"break" || command == L"continue"))
{
// Walk up until we hit a 'for' or 'while' loop. If we hit a function first, stop the search; we can't break an outer loop from inside a function.
// This is a little funny because we can't tell if it's a 'for' or 'while' loop from the ancestor alone; we need the header. That is, we hit a block_statement, and have to check its header.
bool found_loop = false, end_search = false;
const parse_node_t *ancestor = &node;
while (ancestor != NULL && ! end_search)
{
const parse_node_t *loop_or_function_header = node_tree.header_node_for_block_statement(*ancestor);
if (loop_or_function_header != NULL)
{
switch (loop_or_function_header->type)
{
case symbol_while_header:
case symbol_for_header:
// this is a loop header, so we can break or continue
found_loop = true;
end_search = true;
break;
case symbol_function_header:
// this is a function header, so we cannot break or continue. We stop our search here.
found_loop = false;
end_search = true;
break;
default:
// most likely begin / end style block, which makes no difference
break;
}
}
ancestor = node_tree.get_parent(*ancestor);
}
if (! found_loop && ! first_argument_is_help(node_tree, node, buff_src))
{
errored = append_syntax_error(&parse_errors, node, (command == L"break" ? INVALID_BREAK_ERR_MSG : INVALID_CONTINUE_ERR_MSG));
}
}
}
}
}
}
parser_test_error_bits_t res = 0;
if (errored)
res |= PARSER_TEST_ERROR;
if (has_unclosed_block)
res |= PARSER_TEST_INCOMPLETE;
if (out_errors)
{
out_errors->swap(parse_errors);
}
return res;
}

View file

@ -8,6 +8,7 @@
#define FISH_PARSE_UTIL_H
#include "autoload.h"
#include "parse_tree.h"
#include <wchar.h>
#include <map>
#include <set>
@ -27,6 +28,25 @@ int parse_util_locate_cmdsubst(const wchar_t *in,
wchar_t **end,
bool accept_incomplete);
/**
Alternative API. Iterate over command substitutions.
\param str the string to search for subshells
\param inout_cursor_offset On input, the location to begin the search. On output, either the end of the string, or just after the closed-paren.
\param out_contents On output, the contents of the command substitution
\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
*/
int parse_util_locate_cmdsubst_range(const wcstring &str,
size_t *inout_cursor_offset,
wcstring *out_contents,
size_t *out_start,
size_t *out_end,
bool accept_incomplete);
/**
Find the beginning and end of the command substitution under the
cursor. If no subshell is found, the entire string is returned. If
@ -140,5 +160,9 @@ void parse_util_get_parameter_info(const wcstring &cmd, const size_t pos, wchar_
*/
wcstring parse_util_escape_string_with_quote(const wcstring &cmd, wchar_t quote);
/** Given a string, parse it as fish code and then return the indents. The return value has the same size as the string */
std::vector<int> parse_util_compute_indents(const wcstring &src);
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, parse_error_list_t *out_errors = NULL);
#endif

1139
parser.cpp

File diff suppressed because it is too large Load diff

View file

@ -11,11 +11,9 @@
#include "util.h"
#include "event.h"
#include "function.h"
#include "parse_tree.h"
#include <vector>
#define PARSER_TEST_ERROR 1
#define PARSER_TEST_INCOMPLETE 2
/**
event_blockage_t represents a block on events of the specified type
*/
@ -97,38 +95,19 @@ public:
bool skip; /**< Whether execution of the commands in this block should be skipped */
bool had_command; /**< Set to non-zero once a command has been executed in this block */
int tok_pos; /**< The start index of the block */
node_offset_t node_offset; /* Offset of the node */
/**
Status for the current loop block. Can be any of the values from the loop_status enum.
*/
/** Status for the current loop block. Can be any of the values from the loop_status enum. */
int loop_status;
/**
The job that is currently evaluated in the specified block.
*/
/** The job that is currently evaluated in the specified block. */
job_t *job;
#if 0
union
{
int while_state; /**< True if the loop condition has not yet been evaluated*/
wchar_t *for_variable; /**< Name of the variable to loop over */
int if_state; /**< The state of the if block, can be one of IF_STATE_UNTESTED, IF_STATE_FALSE, IF_STATE_TRUE */
wchar_t *switch_value; /**< The value to test in a switch block */
const wchar_t *source_dest; /**< The name of the file to source*/
event_t *event; /**<The event that triggered this block */
wchar_t *function_call_name;
} param1;
#endif
/**
Name of file that created this block
*/
/** Name of file that created this block */
const wchar_t *src_filename;
/**
Line number where this block was created
*/
/** Line number where this block was created */
int src_lineno;
/** Whether we should pop the environment variable stack when we're popped off of the block stack */
@ -291,9 +270,11 @@ struct profile_item_t
};
struct tokenizer_t;
class parse_execution_context_t;
class parser_t
{
friend class parse_execution_context_t;
private:
enum parser_type_t parser_type;
@ -305,6 +286,12 @@ private:
/** Position of last error */
int err_pos;
/** Indication that we should skip all blocks */
bool cancellation_requested;
/** Stack of execution contexts. We own these pointers and must delete them */
std::vector<parse_execution_context_t *> execution_contexts;
/** Description of last error */
wcstring err_buff;
@ -340,6 +327,7 @@ private:
/* No copying allowed */
parser_t(const parser_t&);
parser_t& operator=(const parser_t&);
void parse_job_argument_list(process_t *p, job_t *j, tokenizer_t *tok, std::vector<completion_t>&, bool);
int parse_job(process_t *p, job_t *j, tokenizer_t *tok);
@ -350,7 +338,10 @@ private:
void print_errors_stderr();
/** Create a job */
job_t *job_create();
job_t *job_create(const io_chain_t &io);
/** Adds a job to the beginning of the job list. */
void job_add(job_t *job);
public:
std::vector<profile_item_t*> profile_items;
@ -389,11 +380,15 @@ public:
\return 0 on success, 1 otherwise
*/
int eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type_t block_type);
int eval(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type);
int eval_new_parser(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type);
/** Evaluates a block node at the given node offset in the topmost execution context */
int eval_block_node(node_offset_t node_idx, const io_chain_t &io, enum block_type_t block_type);
/**
Evaluate line as a list of parameters, i.e. tokenize it and perform parameter expansion and cmdsubst execution on the tokens.
The output is inserted into output, and should be freed by the caller.
The output is inserted into output.
\param line Line to evaluate
\param output List to insert output to
@ -402,7 +397,7 @@ public:
\param line Line to evaluate
\param output List to insert output to
*/
int eval_args(const wchar_t *line, std::vector<completion_t> &output);
void eval_args(const wchar_t *line, std::vector<completion_t> &output);
/**
Sets the current evaluation error. This function should only be used by libraries that are called by
@ -411,7 +406,7 @@ public:
\param p The character offset at which the error occured
\param str The printf-style error message filter
*/
void error(int ec, int p, const wchar_t *str, ...);
void error(int ec, size_t p, const wchar_t *str, ...);
/**
Returns a string describing the current parser pisition in the format 'FILENAME (line LINE_NUMBER): LINE'.
@ -464,6 +459,9 @@ public:
/** Remove the outermost block namespace */
void pop_block();
/** Remove the outermost block, asserting it's the given one */
void pop_block(const block_t *b);
/** Return a description of the given blocktype */
const wchar_t *get_block_desc(int block) const;
@ -492,7 +490,7 @@ 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
*/
int test(const wchar_t * buff, int *block_level = NULL, wcstring *out = NULL, const wchar_t *prefix = NULL);
void get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const;
/**
Test if the specified string can be parsed as an argument list,
@ -529,7 +527,7 @@ public:
\param s the string to test
\param min_match is the minimum number of characters that must match in a long style option, i.e. the longest common prefix between --help and any other option. If less than 3, 3 will be assumed.
*/
int is_help(const wchar_t *s, int min_match) const;
static int is_help(const wchar_t *s, int min_match);
/**
Returns the file currently evaluated by the parser. This can be
@ -541,11 +539,14 @@ public:
/**
Write a stack trace starting at the specified block to the specified wcstring
*/
void stack_trace(size_t block_idx, wcstring &buff);
void stack_trace(size_t block_idx, wcstring &buff) const;
int get_block_type(const wchar_t *cmd) const;
const wchar_t *get_block_command(int type) const;
};
/* Temporary */
bool parser_use_ast(void);
#endif

View file

@ -136,7 +136,9 @@ static bool proc_had_barrier = false;
int get_is_interactive(void)
{
ASSERT_IS_MAIN_THREAD();
return is_interactive;
/* is_interactive is initialized to -1; ensure someone has popped/pushed it before then */
assert(is_interactive >= 0);
return is_interactive > 0;
}
bool get_proc_had_barrier()
@ -515,7 +517,8 @@ static void handle_child_status(pid_t pid, int status)
process_t::process_t() :
argv_array(),
argv0_narrow(),
type(0),
type(),
internal_block_node(NODE_OFFSET_INVALID),
actual_cmd(),
pid(0),
pipe_write_fd(0),
@ -637,6 +640,9 @@ int job_reap(bool interactive)
static int locked = 0;
locked++;
/* Preserve the exit status */
const int saved_status = proc_get_last_status();
/*
job_read may fire an event handler, we do not want to call
@ -752,6 +758,9 @@ int job_reap(bool interactive)
if (found)
fflush(stdout);
/* Restore the exit status. */
proc_set_last_status(saved_status);
locked = 0;
return found;

22
proc.h
View file

@ -20,6 +20,7 @@
#include "util.h"
#include "io.h"
#include "common.h"
#include "parse_tree.h"
/**
The status code use when a command was not found
@ -54,7 +55,7 @@
/**
Types of processes
*/
enum
enum process_type_t
{
/**
A regular external command
@ -72,6 +73,10 @@ enum
A block of commands
*/
INTERNAL_BLOCK,
/** A block of commands, represented as a node */
INTERNAL_BLOCK_NODE,
/**
The exec builtin
*/
@ -81,8 +86,7 @@ enum
*/
INTERNAL_BUFFER,
}
;
};
enum
{
@ -151,8 +155,10 @@ public:
INTERNAL_BUILTIN, \c INTERNAL_FUNCTION, \c INTERNAL_BLOCK,
INTERNAL_EXEC, or INTERNAL_BUFFER
*/
int type;
enum process_type_t type;
/* For internal block processes only, the node offset of the block */
node_offset_t internal_block_node;
/** Sets argv */
void set_argv(const wcstring_list_t &argv)
@ -505,18 +511,12 @@ void job_free(job_t* j);
*/
void job_promote(job_t *job);
/**
Create a new job.
*/
job_t *job_create();
/**
Return the job with the specified job id.
If id is 0 or less, return the last job used.
*/
job_t *job_get(job_id_t id);
/**
Return the job with the specified pid.
*/

View file

@ -99,6 +99,7 @@ commence.
#include "path.h"
#include "parse_util.h"
#include "parser_keywords.h"
#include "parse_tree.h"
/**
Maximum length of prefix string when printing completion
@ -518,7 +519,7 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstr
static void reader_repaint()
{
// Update the indentation
parser_t::principal_parser().test(data->command_line.c_str(), &data->indents[0]);
data->indents = parse_util_compute_indents(data->command_line);
// Combine the command and autosuggestion into one string
wcstring full_line = combine_command_and_autosuggestion(data->command_line, data->autosuggestion);
@ -659,117 +660,55 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso
const size_t subcmd_offset = cmdsub_begin - buff;
const wcstring subcmd = wcstring(cmdsub_begin, cmdsub_end - cmdsub_begin);
const wchar_t *subcmd_cstr = subcmd.c_str();
/* Get the token containing the cursor */
const wchar_t *subcmd_tok_begin = NULL, *subcmd_tok_end = NULL;
assert(cursor_pos >= subcmd_offset);
size_t subcmd_cursor_pos = cursor_pos - subcmd_offset;
parse_util_token_extent(subcmd_cstr, subcmd_cursor_pos, &subcmd_tok_begin, &subcmd_tok_end, NULL, NULL);
/* Compute the offset of the token before the cursor within the subcmd */
assert(subcmd_tok_begin >= subcmd_cstr);
assert(subcmd_tok_end >= subcmd_tok_begin);
const size_t subcmd_tok_begin_offset = subcmd_tok_begin - subcmd_cstr;
const size_t subcmd_tok_length = subcmd_tok_end - subcmd_tok_begin;
/* Now parse the subcmd, looking for commands */
bool had_cmd = false, previous_token_is_cmd = false;
tokenizer_t tok(subcmd_cstr, TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS);
for (; tok_has_next(&tok); tok_next(&tok))
const size_t subcmd_cursor_pos = cursor_pos - subcmd_offset;
/* Parse this subcmd */
parse_node_tree_t parse_tree;
parse_tree_from_string(subcmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &parse_tree, NULL);
/* Look for plain statements where the cursor is at the end of the command */
const parse_node_t *matching_cmd_node = NULL;
const size_t len = parse_tree.size();
for (size_t i=0; i < len; i++)
{
size_t tok_pos = static_cast<size_t>(tok_get_pos(&tok));
if (tok_pos > subcmd_tok_begin_offset)
const parse_node_t &node = parse_tree.at(i);
/* Only interested in plain statements with source */
if (node.type != symbol_plain_statement || ! node.has_source())
continue;
/* Skip decorated statements */
if (parse_tree.decoration_for_plain_statement(node) != parse_statement_decoration_none)
continue;
/* Get the command node. Skip it if we can't or it has no source */
const parse_node_t *cmd_node = parse_tree.get_child(node, 0, parse_token_type_string);
if (cmd_node == NULL || ! cmd_node->has_source())
continue;
/* Now see if its source range contains our cursor, including at the end */
if (subcmd_cursor_pos >= cmd_node->source_start && subcmd_cursor_pos <= cmd_node->source_start + cmd_node->source_length)
{
/* We've passed the token we're interested in */
/* Success! */
matching_cmd_node = cmd_node;
break;
}
int last_type = tok_last_type(&tok);
switch (last_type)
{
case TOK_STRING:
{
if (had_cmd)
{
/* Parameter to the command. */
}
else
{
const wcstring potential_cmd = tok_last(&tok);
if (parser_keywords_is_subcommand(potential_cmd))
{
if (potential_cmd == L"command" || potential_cmd == L"builtin")
{
/* 'command' and 'builtin' defeat abbreviation expansion. Skip this command. */
had_cmd = true;
}
else
{
/* Other subcommand. Pretend it doesn't exist so that we can expand the following command */
had_cmd = false;
}
}
else
{
/* It's a normal command */
had_cmd = true;
if (tok_pos == subcmd_tok_begin_offset)
{
/* This is the token we care about! */
previous_token_is_cmd = true;
}
}
}
break;
}
case TOK_REDIRECT_NOCLOB:
case TOK_REDIRECT_OUT:
case TOK_REDIRECT_IN:
case TOK_REDIRECT_APPEND:
case TOK_REDIRECT_FD:
{
if (!had_cmd)
{
break;
}
tok_next(&tok);
break;
}
case TOK_PIPE:
case TOK_BACKGROUND:
case TOK_END:
{
had_cmd = false;
break;
}
case TOK_COMMENT:
case TOK_ERROR:
default:
{
break;
}
}
}
/* Now if we found a command node, expand it */
bool result = false;
if (previous_token_is_cmd)
if (matching_cmd_node != NULL)
{
/* The token is a command. Try expanding it as an abbreviation. */
const wcstring token = wcstring(subcmd, subcmd_tok_begin_offset, subcmd_tok_length);
assert(matching_cmd_node->type == parse_token_type_string);
const wcstring token = matching_cmd_node->get_source(subcmd);
wcstring abbreviation;
if (expand_abbreviation(token, &abbreviation))
{
/* There was an abbreviation! Replace the token in the full command. Maintain the relative position of the cursor. */
if (output != NULL)
{
size_t cmd_tok_begin_offset = subcmd_tok_begin_offset + subcmd_offset;
output->assign(cmdline);
output->replace(cmd_tok_begin_offset, subcmd_tok_length, abbreviation);
output->replace(subcmd_offset + matching_cmd_node->source_start, matching_cmd_node->source_length, abbreviation);
}
result = true;
}
@ -1494,7 +1433,7 @@ struct autosuggestion_context_t
{
const completion_t &comp = completions.at(0);
size_t cursor = this->cursor_pos;
this->autosuggestion = completion_apply_to_command_line(comp.completion.c_str(), comp.flags, this->search_string, &cursor, true /* append only */);
this->autosuggestion = completion_apply_to_command_line(comp.completion, comp.flags, this->search_string, &cursor, true /* append only */);
return 1;
}
@ -2129,11 +2068,9 @@ static void reader_interactive_destroy()
void reader_sanity_check()
{
if (get_is_interactive())
/* Note: 'data' is non-null if we are interactive, except in the testing environment */
if (get_is_interactive() && data != NULL)
{
if (!data)
sanity_lose();
if (!(data->buff_pos <= data->command_length()))
sanity_lose();
@ -2263,7 +2200,6 @@ static void handle_token_history(int forward, int reset)
*/
if (data->history_search.go_backwards())
{
wcstring item = data->history_search.current_string();
data->token_history_buff = data->history_search.current_string();
}
current_pos = data->token_history_buff.size();
@ -2533,28 +2469,26 @@ void reader_run_command(parser_t &parser, const wcstring &cmd)
int reader_shell_test(const wchar_t *b)
{
int res = parser_t::principal_parser().test(b);
assert(b != NULL);
wcstring bstr = b;
/* Append a newline, to act as a statement terminator */
bstr.push_back(L'\n');
parse_error_list_t errors;
int res = parse_util_detect_errors(bstr, &errors);
if (res & PARSER_TEST_ERROR)
{
wcstring sb;
const int tmp[1] = {0};
const int tmp2[1] = {0};
const wcstring empty;
s_write(&data->screen,
empty,
empty,
empty,
0,
tmp,
tmp2,
0);
parser_t::principal_parser().test(b, NULL, &sb, L"fish");
fwprintf(stderr, L"%ls", sb.c_str());
wcstring error_desc;
parser_t::principal_parser().get_backtrace(bstr, errors, &error_desc);
// ensure we end with a newline. Also add an initial newline, because it's likely the user just hit enter and so there's junk on the current line
if (! string_suffixes_string(L"\n", error_desc))
{
error_desc.push_back(L'\n');
}
fwprintf(stderr, L"\n%ls", error_desc.c_str());
}
return res;
}
@ -2804,10 +2738,10 @@ static void reader_super_highlight_me_plenty(size_t match_highlight_pos)
}
int exit_status()
bool shell_is_exiting()
{
if (get_is_interactive())
return job_list_is_empty() && data->end_loop;
return job_list_is_empty() && data != NULL && data->end_loop;
else
return end_loop;
}
@ -3058,6 +2992,7 @@ const wchar_t *reader_readline(void)
is_interactive_read = 1;
c=input_readch();
is_interactive_read = was_interactive_read;
//fprintf(stderr, "C: %lx\n", (long)c);
if (((!wchar_private(c))) && (c>31) && (c != 127))
{
@ -3231,6 +3166,9 @@ const wchar_t *reader_readline(void)
/* Figure out the extent of the token within the command substitution. Note we pass cmdsub_begin here, not buff */
const wchar_t *token_begin, *token_end;
parse_util_token_extent(cmdsub_begin, data->buff_pos - (cmdsub_begin-buff), &token_begin, &token_end, 0, 0);
/* Hack: the token may extend past the end of the command substitution, e.g. in (echo foo) the last token is 'foo)'. Don't let that happen. */
if (token_end > cmdsub_end) token_end = cmdsub_end;
/* Figure out how many steps to get from the current position to the end of the current token. */
size_t end_of_token_offset = token_end - buff;
@ -3386,7 +3324,7 @@ const wchar_t *reader_readline(void)
{
//history_reset();
data->history_search.go_to_end();
reader_set_buffer(data->search_buff.c_str(), data->search_buff.size());
reader_set_buffer(data->search_buff, data->search_buff.size());
}
else
{
@ -3463,12 +3401,9 @@ const wchar_t *reader_readline(void)
case 0:
{
/* Finished command, execute it. Don't add items that start with a leading space. */
if (! data->command_line.empty() && data->command_line.at(0) != L' ')
if (data->history != NULL && ! data->command_line.empty() && data->command_line.at(0) != L' ')
{
if (data->history != NULL)
{
data->history->add_with_file_detection(data->command_line);
}
data->history->add_with_file_detection(data->command_line);
}
finished=1;
data->buff_pos=data->command_length();
@ -3958,13 +3893,15 @@ static int read_ni(int fd, const io_chain_t &io)
res = 1;
}
wcstring sb;
if (! parser.test(str.c_str(), 0, &sb, L"fish"))
parse_error_list_t errors;
if (! parse_util_detect_errors(str, &errors))
{
parser.eval(str, io, TOP);
}
else
{
wcstring sb;
parser.get_backtrace(str, errors, &sb);
fwprintf(stderr, L"%ls", sb.c_str());
res = 1;
}

View file

@ -217,7 +217,7 @@ void reader_set_exit_on_interrupt(bool flag);
/**
Returns true if the shell is exiting, 0 otherwise.
*/
int exit_status();
bool shell_is_exiting();
/**
The readers interrupt signal handler. Cancels all currently running blocks.

View file

@ -15,7 +15,7 @@ echo x-{1}
echo x-{1,2}
echo foo-{1,2{3,4}}
# Escpaed newlines
# Escaped newlines
echo foo\ bar
echo foo\
bar
@ -99,6 +99,12 @@ echo Test 5 $sta
echo Test redirections
begin ; echo output ; echo errput 1>&2 ; end 2>&1 | tee /tmp/tee_test.txt ; cat /tmp/tee_test.txt
# Verify that we can pipe something other than stdout
# The first line should be printed, since we output to stdout but pipe stderr to /dev/null
# The second line should not be printed, since we output to stderr and pipe it to /dev/null
begin ; echo is_stdout ; end 2>| cat > /dev/null
begin ; echo is_stderr 1>&2 ; end 2>| cat > /dev/null
# echo tests
echo 'abc\ndef'

View file

@ -23,6 +23,7 @@ errput
output
errput
output
is_stdout
abc\ndef
abc
def

View file

@ -20,15 +20,6 @@ case one
echo $status
end
# Test that non-case tokens inside `switch` don't blow away status
# (why are these even allowed?)
false
switch one
true
case one
echo $status
end
#test contains -i
echo test contains -i
contains -i string a b c string d

View file

@ -3,7 +3,6 @@
3
0
1
1
test contains -i
4

View file

@ -35,3 +35,39 @@ emit test3 foo bar
# test empty argument
emit
echo "Test break and continue"
# This should output Ping once
for i in a b c
if not contains $i c ; continue ; end
echo Ping
end
# This should output Pong not at all
for i in a b c
if not contains $i c ; break ; end
echo Pong
end
# This should output Foop three times, and Boop not at all
set i a a a
while contains $i a
set -e i[-1]
echo Foop
continue
echo Boop
end
# This should output Doop once
set i a a a
while contains $i a
set -e i[-1]
echo Doop
break
echo Darp
end
# Test implicit cd. This should do nothing.
./
false

View file

@ -2,3 +2,9 @@ Testing that builtins can truncate files
abc
before:test1
received event test3 with args: foo bar
Test break and continue
Ping
Foop
Foop
Foop
Doop

View file

@ -14,7 +14,7 @@ segments.
#include <wctype.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "fallback.h"
#include "util.h"
@ -50,7 +50,7 @@ segments.
/**
Error string for when trying to pipe from fd 0
*/
#define PIPE_ERROR _( L"Can not use fd 0 as pipe output" )
#define PIPE_ERROR _( L"Cannot use stdin (fd 0) as pipe output" )
/**
Characters that separate tokens. They are ordered by frequency of occurrence to increase parsing speed.
@ -64,7 +64,6 @@ static const wchar_t *tok_desc[] =
{
N_(L"Tokenizer not yet initialized"),
N_(L"Tokenizer error"),
N_(L"Invalid token"),
N_(L"String"),
N_(L"Pipe"),
N_(L"End of command"),
@ -77,6 +76,8 @@ static const wchar_t *tok_desc[] =
N_(L"Comment")
};
/**
Set the latest tokens string to be the specified error message
*/
@ -95,16 +96,8 @@ int tok_get_error(tokenizer_t *tok)
tokenizer_t::tokenizer_t(const wchar_t *b, tok_flags_t flags) : buff(NULL), orig_buff(NULL), last_type(TOK_NONE), last_pos(0), has_next(false), accept_unfinished(false), show_comments(false), last_quote(0), error(0), squash_errors(false), cached_lineno_offset(0), cached_lineno_count(0)
{
/* We can only generate error messages on the main thread due to wgettext() thread safety issues. */
if (!(flags & TOK_SQUASH_ERRORS))
{
ASSERT_IS_MAIN_THREAD();
}
CHECK(b,);
this->accept_unfinished = !!(flags & TOK_ACCEPT_UNFINISHED);
this->show_comments = !!(flags & TOK_SHOW_COMMENTS);
this->squash_errors = !!(flags & TOK_SQUASH_ERRORS);
@ -435,65 +428,141 @@ static void read_comment(tokenizer_t *tok)
tok->last_type = TOK_COMMENT;
}
/**
Read a FD redirection.
/* Reads a redirection or an "fd pipe" (like 2>|) from a string. Returns how many characters were consumed. If zero, then this string was not a redirection.
Also returns by reference the redirection mode, and the fd to redirection. If there is overflow, *out_fd is set to -1.
*/
static void read_redirect(tokenizer_t *tok, int fd)
static size_t read_redirection_or_fd_pipe(const wchar_t *buff, enum token_type *out_redirection_mode, int *out_fd)
{
bool errored = false;
int fd = 0;
enum token_type redirection_mode = TOK_NONE;
if ((*tok->buff == L'>') ||
(*tok->buff == L'^'))
size_t idx = 0;
/* Determine the fd. This may be specified as a prefix like '2>...' or it may be implicit like '>' or '^'. Try parsing out a number; if we did not get any digits then infer it from the first character. Watch out for overflow. */
long long big_fd = 0;
for (; iswdigit(buff[idx]); idx++)
{
tok->buff++;
if (*tok->buff == *(tok->buff-1))
/* Note that it's important we consume all the digits here, even if it overflows. */
if (big_fd <= INT_MAX)
big_fd = big_fd * 10 + (buff[idx] - L'0');
}
fd = (big_fd > INT_MAX ? -1 : static_cast<int>(big_fd));
if (idx == 0)
{
/* We did not find a leading digit, so there's no explicit fd. Infer it from the type */
switch (buff[idx])
{
tok->buff++;
redirection_mode = TOK_REDIRECT_APPEND;
}
else
{
redirection_mode = TOK_REDIRECT_OUT;
}
if (*tok->buff == L'|')
{
if (fd == 0)
{
TOK_CALL_ERROR(tok, TOK_OTHER, PIPE_ERROR);
return;
}
tok->buff++;
tok->last_token = to_string<int>(fd);
tok->last_type = TOK_PIPE;
return;
case L'>': fd = STDOUT_FILENO; break;
case L'<': fd = STDIN_FILENO; break;
case L'^': fd = STDERR_FILENO; break;
default: errored = true; break;
}
}
else if (*tok->buff == L'<')
/* Either way we should have ended on the redirection character itself like '>' */
wchar_t redirect_char = buff[idx++]; //note increment of idx
if (redirect_char == L'>' || redirect_char == L'^')
{
redirection_mode = TOK_REDIRECT_OUT;
if (buff[idx] == redirect_char)
{
/* Doubled up like ^^ or >>. That means append */
redirection_mode = TOK_REDIRECT_APPEND;
idx++;
}
}
else if (redirect_char == L'<')
{
tok->buff++;
redirection_mode = TOK_REDIRECT_IN;
}
else
{
TOK_CALL_ERROR(tok, TOK_OTHER, REDIRECT_ERROR);
/* Something else */
errored = true;
}
/* Optional characters like & or ?, or the pipe char | */
wchar_t opt_char = buff[idx];
if (opt_char == L'&')
{
redirection_mode = TOK_REDIRECT_FD;
idx++;
}
else if (opt_char == L'?')
{
redirection_mode = TOK_REDIRECT_NOCLOB;
idx++;
}
else if (opt_char == L'|')
{
/* So the string looked like '2>|'. This is not a redirection - it's a pipe! That gets handled elsewhere. */
redirection_mode = TOK_PIPE;
idx++;
}
/* Don't return valid-looking stuff on error */
if (errored)
{
idx = 0;
redirection_mode = TOK_NONE;
}
/* Return stuff */
if (out_redirection_mode != NULL)
*out_redirection_mode = redirection_mode;
if (out_fd != NULL)
*out_fd = fd;
return idx;
}
tok->last_token = to_string(fd);
enum token_type redirection_type_for_string(const wcstring &str, int *out_fd)
{
enum token_type mode = TOK_NONE;
int fd = 0;
read_redirection_or_fd_pipe(str.c_str(), &mode, &fd);
/* Redirections only, no pipes */
if (mode == TOK_PIPE || fd < 0)
mode = TOK_NONE;
if (out_fd != NULL)
*out_fd = fd;
return mode;
}
if (*tok->buff == L'&')
int fd_redirected_by_pipe(const wcstring &str)
{
/* Hack for the common case */
if (str == L"|")
{
tok->buff++;
tok->last_type = TOK_REDIRECT_FD;
return STDOUT_FILENO;
}
else if (*tok->buff == L'?')
enum token_type mode = TOK_NONE;
int fd = 0;
read_redirection_or_fd_pipe(str.c_str(), &mode, &fd);
/* Pipes only */
if (mode != TOK_PIPE || fd < 0)
fd = -1;
return fd;
}
int oflags_for_redirection_type(enum token_type type)
{
switch (type)
{
tok->buff++;
tok->last_type = TOK_REDIRECT_NOCLOB;
}
else
{
tok->last_type = redirection_mode;
case TOK_REDIRECT_APPEND: return O_CREAT | O_APPEND | O_WRONLY;
case TOK_REDIRECT_OUT: return O_CREAT | O_WRONLY | O_TRUNC;
case TOK_REDIRECT_NOCLOB: return O_CREAT | O_EXCL | O_WRONLY;
case TOK_REDIRECT_IN: return O_RDONLY;
default:
return -1;
}
}
@ -516,7 +585,7 @@ static bool my_iswspace(wchar_t c)
const wchar_t *tok_get_desc(int type)
{
if (type < 0 || (size_t)type >= sizeof(tok_desc))
if (type < 0 || (size_t)type >= (sizeof tok_desc / sizeof *tok_desc))
{
return _(L"Invalid token type");
}
@ -606,36 +675,56 @@ void tok_next(tokenizer_t *tok)
break;
case L'>':
read_redirect(tok, 1);
return;
case L'<':
read_redirect(tok, 0);
return;
case L'^':
read_redirect(tok, 2);
return;
{
/* There's some duplication with the code in the default case below. The key difference here is that we must never parse these as a string; a failed redirection is an error! */
enum token_type mode = TOK_NONE;
int fd = -1;
size_t consumed = read_redirection_or_fd_pipe(tok->buff, &mode, &fd);
if (consumed == 0 || fd < 0)
{
TOK_CALL_ERROR(tok, TOK_OTHER, REDIRECT_ERROR);
}
else
{
tok->buff += consumed;
tok->last_type = mode;
tok->last_token = to_string(fd);
}
}
break;
default:
{
/* Maybe a redirection like '2>&1', maybe a pipe like 2>|, maybe just a string */
size_t consumed = 0;
enum token_type mode = TOK_NONE;
int fd = -1;
if (iswdigit(*tok->buff))
consumed = read_redirection_or_fd_pipe(tok->buff, &mode, &fd);
if (consumed > 0)
{
const wchar_t *orig = tok->buff;
int fd = 0;
while (iswdigit(*tok->buff))
fd = (fd*10) + (*(tok->buff++) - L'0');
switch (*(tok->buff))
/* It looks like a redirection or a pipe. But we don't support piping fd 0. Note that fd 0 may be -1, indicating overflow; but we don't treat that as a tokenizer error. */
if (mode == TOK_PIPE && fd == 0)
{
case L'^':
case L'>':
case L'<':
read_redirect(tok, fd);
return;
TOK_CALL_ERROR(tok, TOK_OTHER, PIPE_ERROR);
}
else
{
tok->buff += consumed;
tok->last_type = mode;
tok->last_token = to_string(fd);
}
tok->buff = orig;
}
read_string(tok);
else
{
/* Not a redirection or pipe, so just a stirng */
read_string(tok);
}
}
break;
}
@ -693,13 +782,19 @@ wcstring tok_first(const wchar_t *str)
return result;
}
int tok_get_pos(tokenizer_t *tok)
int tok_get_pos(const tokenizer_t *tok)
{
CHECK(tok, 0);
return (int)tok->last_pos;
}
size_t tok_get_extent(const tokenizer_t *tok)
{
CHECK(tok, 0);
size_t current_pos = tok->buff - tok->orig_buff;
return current_pos > tok->last_pos ? current_pos - tok->last_pos : 0;
}
void tok_set_pos(tokenizer_t *tok, int pos)
{

View file

@ -19,10 +19,9 @@ enum token_type
{
TOK_NONE, /**< Tokenizer not yet constructed */
TOK_ERROR, /**< Error reading token */
TOK_INVALID,/**< Invalid token */
TOK_STRING,/**< String token */
TOK_PIPE,/**< Pipe token */
TOK_END,/**< End token */
TOK_END,/**< End token (semicolon or newline, not literal end) */
TOK_REDIRECT_OUT, /**< redirection token */
TOK_REDIRECT_APPEND,/**< redirection append token */
TOK_REDIRECT_IN,/**< input redirection token */
@ -143,7 +142,10 @@ int tok_has_next(tokenizer_t *tok);
/**
Returns the position of the beginning of the current token in the original string
*/
int tok_get_pos(tokenizer_t *tok);
int tok_get_pos(const tokenizer_t *tok);
/** Returns the extent of the current token */
size_t tok_get_extent(const tokenizer_t *tok);
/** Returns the token type after the current one, without adjusting the position. Optionally returns the next string by reference. */
enum token_type tok_peek_next(tokenizer_t *tok, wcstring *out_next_string);
@ -185,6 +187,15 @@ const wchar_t *tok_get_desc(int type);
*/
int tok_get_error(tokenizer_t *tok);
/* Helper function to determine redirection type from a string, or TOK_NONE if the redirection is invalid. Also returns the fd by reference. */
enum token_type redirection_type_for_string(const wcstring &str, int *out_fd = NULL);
/* Helper function to determine which fd is redirected by a pipe */
int fd_redirected_by_pipe(const wcstring &str);
/* Helper function to return oflags (as in open(2)) for a redirection type */
int oflags_for_redirection_type(enum token_type type);
enum move_word_style_t
{
move_word_style_punctuation, //stop at punctuation

View file

@ -476,7 +476,7 @@ const wchar_t *wgettext(const wchar_t *in)
{
cstring mbs_in = wcs2string(key);
char *out = fish_gettext(mbs_in.c_str());
val = new wcstring(format_string(L"%s", out));
val = new wcstring(format_string(L"%s", out)); //note that this writes into the map!
}
errno = err;
return val->c_str();