Command highlighting now works

This commit is contained in:
ridiculousfish 2013-10-06 16:23:45 -07:00
parent dd91779442
commit 14741518a7
6 changed files with 299 additions and 74 deletions

View file

@ -1922,11 +1922,10 @@ static void test_new_parser_fuzzing(void)
{
parse_t parser;
parse_node_tree_t parse_tree;
parse_error_list_t errors;
for (size_t i=0; i < len; i++)
{
const parser_fuzz_token_t &token = tokens[i];
parser.parse_1_token(token.token_type, token.keyword, &parse_tree, &errors);
parser.parse_1_token(token.token_type, token.keyword, &parse_tree, NULL);
}
// keep going until we wrap
@ -1940,7 +1939,7 @@ static void test_new_parser_fuzzing(void)
__attribute__((unused))
static void test_new_parser(void)
{
say(L"Testing new parser!");
say(L"Testing new parser");
const wcstring src = L"echo hello world";
parse_node_tree_t parse_tree;
parse_t parser;
@ -2050,7 +2049,7 @@ static void test_highlighting(void)
if (expected_colors.at(i) != colors.at(i))
{
const wcstring spaces(i, L' ');
err(L"Wrong color at index %lu in text (expected %d, actual %d):\n%ls\n%ls^", i, expected_colors.at(i), colors.at(i), text.c_str(), spaces.c_str());
err(L"Wrong color at index %lu in text (expected %#x, actual %#x):\n%ls\n%ls^", i, expected_colors.at(i), colors.at(i), text.c_str(), spaces.c_str());
}
}
}
@ -2079,10 +2078,12 @@ int main(int argc, char **argv)
reader_init();
env_init();
//test_new_parser_fuzzing();
//test_new_parser_correctness();
//test_highlighting();
//test_new_parser();
test_highlighting();
return 0;
test_new_parser_fuzzing();
test_new_parser_correctness();
test_highlighting();
test_new_parser();
test_format();
test_escape();

View file

@ -667,18 +667,19 @@ static void highlight_param(const wcstring &buffstr, std::vector<int> &colors, w
}
}
static int has_expand_reserved(const wchar_t *str)
static bool has_expand_reserved(const wcstring &str)
{
while (*str)
bool result = false;
for (size_t i=0; i < str.size(); i++)
{
if (*str >= EXPAND_RESERVED &&
*str <= EXPAND_RESERVED_END)
wchar_t wc = str.at(i);
if (wc >= EXPAND_RESERVED && wc <= EXPAND_RESERVED_END)
{
return 1;
result = true;
break;
}
str++;
}
return 0;
return result;
}
/* Parse a command line. Return by reference the last command, its arguments, and the offset in the string of the beginning of the last argument. This is used by autosuggestions */
@ -712,7 +713,7 @@ static bool autosuggest_parse_command(const wcstring &str, wcstring *out_command
/* Command. First check that the command actually exists. */
wcstring local_cmd = tok_last(&tok);
bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
if (! expanded || has_expand_reserved(cmd.c_str()))
if (! expanded || has_expand_reserved(cmd))
{
/* We can't expand this cmd, ignore it */
}
@ -1027,7 +1028,7 @@ static void tokenize(const wchar_t * const buff, std::vector<int> &color, const
*/
cmd = tok_last(&tok);
bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS);
if (! expanded || has_expand_reserved(cmd.c_str()))
if (! expanded || has_expand_reserved(cmd))
{
color.at(tok_get_pos(&tok)) = HIGHLIGHT_ERROR;
}
@ -1308,17 +1309,22 @@ static void tokenize(const wchar_t * const buff, std::vector<int> &color, const
}
}
void highlight_shell_magic(const wcstring &buff, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
// PCA This function does I/O, (calls is_potential_path, path_get_path, maybe others) and so ought to only run on a background thread
void highlight_shell(const wcstring &buff, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars)
{
ASSERT_IS_BACKGROUND_THREAD();
if (0)
if (1)
{
highlight_shell_magic(buff, color, pos, error, vars);
return;
}
else
{
highlight_shell_classic(buff, color, pos, error, vars);
}
}
// PCA This function does I/O, (calls is_potential_path, path_get_path, maybe others) and so ought to only run on a background thread
void highlight_shell_classic(const wcstring &buff, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars)
{
ASSERT_IS_BACKGROUND_THREAD();
const size_t length = buff.size();
assert(buff.size() == color.size());
@ -1461,6 +1467,7 @@ static void color_node(const parse_node_t &node, int color, std::vector<int> &co
std::fill(color_array.begin() + node.source_start, color_array.begin() + source_end, color);
}
/* This function is a disaster badly in need of refactoring */
static void color_argument(const wcstring &buffstr, std::vector<int>::iterator colors, int normal_status)
{
const size_t buff_len = buffstr.size();
@ -1743,6 +1750,28 @@ static void color_argument(const wcstring &buffstr, std::vector<int>::iterator c
}
}
// Indicates whether the source range of the given node forms a valid path in the given working_directory
static bool node_is_potential_path(const wcstring &src, const parse_node_t &node, const wcstring &working_directory)
{
if (! node.has_source())
return false;
/* Get the node source, unescape it, and then pass it to is_potential_path along with the working directory (as a one element list) */
bool result = false;
wcstring token(src, node.source_start, node.source_length);
if (unescape_string(token, 1))
{
/* Big hack: is_potential_path expects a tilde, but unescape_string gives us HOME_DIRECTORY. Put it back. */
if (! token.empty() && token.at(0) == HOME_DIRECTORY)
token.at(0) = L'~';
const wcstring_list_t working_directory_list(1, working_directory);
result = is_potential_path(token, working_directory_list, PATH_EXPAND_TILDE);
}
return result;
}
// Color all of the arguments of the given command
static void color_arguments(const wcstring &src, const parse_node_tree_t &tree, const parse_node_t &parent, std::vector<int> &color_array)
{
@ -1754,22 +1783,87 @@ static void color_arguments(const wcstring &src, const parse_node_tree_t &tree,
const parse_node_t *child = nodes.at(i);
assert(child != NULL && child->type == symbol_argument);
param.assign(src, child->source_start, child->source_length);
color_argument(param, color_array.begin() + child->source_start, HIGHLIGHT_NORMAL);
color_argument(param, color_array.begin() + child->source_start, HIGHLIGHT_PARAM);
}
}
/* Color all the children of the command with the given type */
static void color_children(const parse_node_tree_t &tree, const parse_node_t &parent, parse_token_type_t type, int color, std::vector<int> &color_array)
{
for (node_offset_t idx=0; idx < parent.child_count; idx++)
{
const parse_node_t *child = tree.get_child(parent, idx);
if (child != NULL && child->type == type && child->has_source())
if (child != NULL && child->type == type)
{
color_node(*child, color, color_array);
}
}
}
/* Color a possibly decorated command */
static void color_command(const wcstring &src, const parse_node_tree_t &tree, const parse_node_t &cmd_node, enum parse_statement_decoration_t decoration, std::vector<int> &color_array, const wcstring &working_directory, const env_vars_snapshot_t &vars)
{
if (! cmd_node.has_source())
return;
/* Get the source of the command */
wcstring cmd(src, cmd_node.source_start, cmd_node.source_length);
/* Try expanding it. If we cannot, it's an error. */
bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS);
if (! expanded || has_expand_reserved(cmd))
{
color_node(cmd_node, HIGHLIGHT_ERROR, color_array);
return;
}
/* Determine which types we check, based on the decoration */
bool builtin_ok = true, function_ok = true, abbreviation_ok = true, command_ok = true, implicit_cd_ok = true;
if (decoration == parse_statement_decoration_command)
{
builtin_ok = false;
function_ok = false;
abbreviation_ok = false;
command_ok = true;
implicit_cd_ok = false;
}
else if (decoration == parse_statement_decoration_builtin)
{
builtin_ok = true;
function_ok = false;
abbreviation_ok = false;
command_ok = false;
implicit_cd_ok = false;
}
/* Check them */
bool is_valid = false;
/* Builtins */
if (! is_valid && builtin_ok)
is_valid = builtin_exists(cmd);
/* Functions */
if (! is_valid && function_ok)
is_valid = function_exists_no_autoload(cmd, vars);
/* Abbreviations */
if (! is_valid && abbreviation_ok)
is_valid = expand_abbreviation(cmd, NULL);
/* Regular commands */
if (! is_valid && command_ok)
is_valid = path_get_path(cmd, NULL, vars);
/* Implicit cd */
if (! is_valid && implicit_cd_ok)
is_valid = path_can_be_implicit_cd(cmd, NULL, working_directory.c_str(), vars);
/* Color the node */
int color = is_valid ? HIGHLIGHT_COMMAND : HIGHLIGHT_ERROR;
color_node(cmd_node, color, color_array);
}
void highlight_shell_magic(const wcstring &buff, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars)
{
ASSERT_IS_BACKGROUND_THREAD();
@ -1780,7 +1874,7 @@ void highlight_shell_magic(const wcstring &buff, std::vector<int> &color, size_t
if (length == 0)
return;
std::fill(color.begin(), color.end(), -1);
std::fill(color.begin(), color.end(), 0);
/* Do something sucky and get the current working directory on this background thread. This should really be passed in. */
const wcstring working_directory = env_get_pwd_slash();
@ -1790,6 +1884,11 @@ void highlight_shell_magic(const wcstring &buff, std::vector<int> &color, size_t
parse_t parser;
parser.parse(buff, parse_flag_continue_after_error | parse_flag_include_comments, &parse_tree, NULL);
#if 0
const wcstring dump = parse_dump_tree(parse_tree, buff);
fprintf(stderr, "%ls\n", dump.c_str());
#endif
/* Walk the node tree */
for (parse_node_tree_t::const_iterator iter = parse_tree.begin(); iter != parse_tree.end(); ++iter)
{
@ -1837,7 +1936,12 @@ void highlight_shell_magic(const wcstring &buff, std::vector<int> &color, size_t
case symbol_plain_statement:
{
// Color the command
color_children(parse_tree, node, parse_token_type_string, HIGHLIGHT_COMMAND, color);
const parse_node_t *cmd = parse_tree.get_child(node, 0, parse_token_type_string);
if (cmd != NULL)
{
enum parse_statement_decoration_t decoration = static_cast<enum parse_statement_decoration_t>(node.tag);
color_command(buff, parse_tree, *cmd, decoration, color, working_directory, vars);
}
// Color arguments
const parse_node_t *arguments = parse_tree.get_child(node, 1, symbol_arguments_or_redirections_list);
@ -1867,6 +1971,32 @@ void highlight_shell_magic(const wcstring &buff, std::vector<int> &color, size_t
break;
}
}
if (pos <= buff.size())
{
/* If the cursor is over an argument, and that argument is a valid path, underline it */
for (parse_node_tree_t::const_iterator iter = parse_tree.begin(); iter != parse_tree.end(); ++iter)
{
const parse_node_t &node = *iter;
/* See if this node contains the cursor */
if (node.type == symbol_argument && node.source_contains_location(pos))
{
/* See if this is a valid path */
if (node_is_potential_path(buff, node, working_directory))
{
/* It is, underline it. */
for (size_t i=node.source_start; i < node.source_start + node.source_length; i++)
{
/* Don't color HIGHLIGHT_ERROR because it looks dorky. For example, trying to cd into a non-directory would show an underline and also red. */
if (! (color.at(i) & HIGHLIGHT_ERROR))
{
color.at(i) |= HIGHLIGHT_VALID_PATH;
}
}
}
}
}
}
}
/**

View file

@ -134,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_magic(const wcstring &buff, std::vector<int> &color, size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars);
#endif

View file

@ -30,7 +30,7 @@ static bool production_is_valid(const production_options_t production_list, prod
#define RESOLVE(sym) static production_option_idx_t resolve_##sym (parse_token_type_t token_type, parse_keyword_t token_keyword, production_tag_t *tag)
#define RESOLVE_ONLY(sym) static production_option_idx_t resolve_##sym (parse_token_type_t token_type, parse_keyword_t token_keyword, production_tag_t *tag) { return 0; }
#define PRODUCE_KEYWORD(x) ((x) + LAST_TOKEN_OR_SYMBOL + 1)
#define KEYWORD(x) ((x) + LAST_TOKEN_OR_SYMBOL + 1)
/* A job_list is a list of jobs, separated by semicolons or newlines */
@ -167,20 +167,20 @@ RESOLVE(statement)
PRODUCTIONS(if_statement) =
{
{symbol_if_clause, symbol_else_clause, PRODUCE_KEYWORD(parse_keyword_end), symbol_arguments_or_redirections_list}
{symbol_if_clause, symbol_else_clause, KEYWORD(parse_keyword_end), symbol_arguments_or_redirections_list}
};
RESOLVE_ONLY(if_statement)
PRODUCTIONS(if_clause) =
{
{ PRODUCE_KEYWORD(parse_keyword_if), symbol_job, parse_token_type_end, symbol_job_list }
{ KEYWORD(parse_keyword_if), symbol_job, parse_token_type_end, symbol_job_list }
};
RESOLVE_ONLY(if_clause)
PRODUCTIONS(else_clause) =
{
{ },
{ PRODUCE_KEYWORD(parse_keyword_else), symbol_else_continuation }
{ KEYWORD(parse_keyword_else), symbol_else_continuation }
};
RESOLVE(else_clause)
{
@ -211,7 +211,7 @@ RESOLVE(else_continuation)
PRODUCTIONS(switch_statement) =
{
{ PRODUCE_KEYWORD(parse_keyword_switch), parse_token_type_string, parse_token_type_end, symbol_case_item_list, PRODUCE_KEYWORD(parse_keyword_end)}
{ KEYWORD(parse_keyword_switch), parse_token_type_string, parse_token_type_end, symbol_case_item_list, KEYWORD(parse_keyword_end)}
};
RESOLVE_ONLY(switch_statement)
@ -230,7 +230,7 @@ RESOLVE(case_item_list)
PRODUCTIONS(case_item) =
{
{PRODUCE_KEYWORD(parse_keyword_case), symbol_argument_list, parse_token_type_end, symbol_job_list}
{KEYWORD(parse_keyword_case), symbol_argument_list, parse_token_type_end, symbol_job_list}
};
RESOLVE_ONLY(case_item)
@ -258,7 +258,7 @@ RESOLVE(argument_list)
PRODUCTIONS(block_statement) =
{
{symbol_block_header, parse_token_type_end, symbol_job_list, PRODUCE_KEYWORD(parse_keyword_end), symbol_arguments_or_redirections_list}
{symbol_block_header, parse_token_type_end, symbol_job_list, KEYWORD(parse_keyword_end), symbol_arguments_or_redirections_list}
};
RESOLVE_ONLY(block_statement)
@ -290,34 +290,34 @@ RESOLVE(block_header)
PRODUCTIONS(for_header) =
{
{PRODUCE_KEYWORD(parse_keyword_for), parse_token_type_string, PRODUCE_KEYWORD(parse_keyword_in), symbol_arguments_or_redirections_list}
{KEYWORD(parse_keyword_for), parse_token_type_string, KEYWORD(parse_keyword_in), symbol_arguments_or_redirections_list}
};
RESOLVE_ONLY(for_header)
PRODUCTIONS(while_header) =
{
{PRODUCE_KEYWORD(parse_keyword_while), symbol_statement}
{KEYWORD(parse_keyword_while), symbol_statement}
};
RESOLVE_ONLY(while_header)
PRODUCTIONS(begin_header) =
{
{PRODUCE_KEYWORD(parse_keyword_begin)}
{KEYWORD(parse_keyword_begin)}
};
RESOLVE_ONLY(begin_header)
PRODUCTIONS(function_header) =
{
{PRODUCE_KEYWORD(parse_keyword_function), parse_token_type_string, symbol_argument_list}
{KEYWORD(parse_keyword_function), parse_token_type_string, symbol_argument_list}
};
RESOLVE_ONLY(function_header)
/* A boolean statement is AND or OR or NOT */
PRODUCTIONS(boolean_statement) =
{
{PRODUCE_KEYWORD(parse_keyword_and), symbol_statement},
{PRODUCE_KEYWORD(parse_keyword_or), symbol_statement},
{PRODUCE_KEYWORD(parse_keyword_not), symbol_statement}
{KEYWORD(parse_keyword_and), symbol_statement},
{KEYWORD(parse_keyword_or), symbol_statement},
{KEYWORD(parse_keyword_not), symbol_statement}
};
RESOLVE(boolean_statement)
{
@ -336,19 +336,19 @@ RESOLVE(boolean_statement)
PRODUCTIONS(decorated_statement) =
{
{PRODUCE_KEYWORD(parse_keyword_command), symbol_plain_statement},
{PRODUCE_KEYWORD(parse_keyword_builtin), symbol_plain_statement},
{symbol_plain_statement}
{symbol_plain_statement},
{KEYWORD(parse_keyword_command), symbol_plain_statement},
{KEYWORD(parse_keyword_builtin), symbol_plain_statement},
};
RESOLVE(decorated_statement)
{
switch (token_keyword)
{
case parse_keyword_command:
return 0;
case parse_keyword_builtin:
return 1;
default:
return 0;
case parse_keyword_command:
return 1;
case parse_keyword_builtin:
return 2;
}
}

View file

@ -339,9 +339,12 @@ class parse_ll_t
/* Whether we ran into a fatal error, including parse errors or tokenizer errors */
bool fatal_errored;
/* Whether we should collect error messages or not */
bool should_generate_error_messages;
/* List of errors we have encountered */
parse_error_list_t errors;
/* The symbol stack can contain terminal types or symbols. Symbols go on to do productions, but terminal types are just matched against input tokens. */
bool top_node_handle_terminal_types(parse_token_t token);
@ -453,7 +456,7 @@ class parse_ll_t
public:
/* Constructor */
parse_ll_t() : fatal_errored(false)
parse_ll_t() : fatal_errored(false), should_generate_error_messages(true)
{
this->symbol_stack.reserve(16);
this->nodes.reserve(64);
@ -469,6 +472,12 @@ class parse_ll_t
return this->fatal_errored;
}
/* Indicate whether we want to generate error messages */
void set_should_generate_error_messages(bool flag)
{
this->should_generate_error_messages = flag;
}
/* Clear the parse symbol stack (but not the node tree). Add a new job_list_t goal node. This is called from the constructor */
void reset_symbols(void);
@ -564,30 +573,36 @@ void parse_ll_t::acquire_output(parse_node_tree_t *output, parse_error_list_t *e
void parse_ll_t::parse_error(parse_token_t token, const wchar_t *fmt, ...)
{
//this->dump_stack();
parse_error_t err;
va_list va;
va_start(va, fmt);
err.text = vformat_string(fmt, va);
va_end(va);
err.source_start = token.source_start;
err.source_length = token.source_length;
this->errors.push_back(err);
this->fatal_errored = true;
if (this->should_generate_error_messages)
{
//this->dump_stack();
parse_error_t err;
va_list va;
va_start(va, fmt);
err.text = vformat_string(fmt, va);
va_end(va);
err.source_start = token.source_start;
err.source_length = token.source_length;
this->errors.push_back(err);
}
}
void parse_ll_t::parse_error(const wchar_t *expected, parse_token_t token)
{
wcstring desc = token_type_description(token.type);
parse_error_t error;
error.text = format_string(L"Expected a %ls, instead got a token of type %ls", expected, desc.c_str());
error.source_start = token.source_start;
error.source_start = token.source_length;
errors.push_back(error);
fatal_errored = true;
if (this->should_generate_error_messages)
{
wcstring desc = token_type_description(token.type);
parse_error_t error;
error.text = format_string(L"Expected a %ls, instead got a token of type %ls", expected, desc.c_str());
error.source_start = token.source_start;
error.source_start = token.source_length;
errors.push_back(error);
}
}
void parse_ll_t::reset_symbols(void)
@ -725,7 +740,14 @@ void parse_ll_t::accept_token(parse_token_t token)
const production_t *production = production_for_token(stack_elem.type, token.type, token.keyword, &node.production_idx, &node.tag, NULL /* error text */);
if (production == NULL)
{
this->parse_error(token, L"Unable to produce a '%ls' from input '%ls'", stack_elem.describe().c_str(), token.describe().c_str());
if (should_generate_error_messages)
{
this->parse_error(token, L"Unable to produce a '%ls' from input '%ls'", stack_elem.describe().c_str(), token.describe().c_str());
}
else
{
this->parse_error(token, NULL);
}
// parse_error sets fatal_errored, which ends the loop
}
else
@ -737,7 +759,7 @@ void parse_ll_t::accept_token(parse_token_t token)
// If we end up with an empty stack, something bad happened, like an unbalanced end
if (symbol_stack.empty())
{
this->parse_error(token, L"All symbols removed from symbol stack. Likely unbalanced else or end?", stack_elem.describe().c_str(), token.describe().c_str());
this->parse_error(token, L"All symbols removed from symbol stack. Likely unbalanced else or end?");
}
}
}
@ -793,11 +815,46 @@ static parse_keyword_t keyword_for_token(token_type tok, const wchar_t *tok_txt)
return result;
}
// Set type-specific tags for nodes
// This is not in parse_ll_t because it knows about different node types
static void tag_nodes(const wcstring &src, parse_node_tree_t *tree)
{
size_t count = tree->size();
for (size_t i=0; i < count; i++)
{
const parse_node_t &node = tree->at(i);
switch (node.type)
{
case symbol_decorated_statement:
{
// Set a tag on the plain statement to indicate the decoration type
// The decoration types matches the production
bool is_decorated = (node.production_idx > 0);
// Get the plain statement and set the tag equal to the production index we used
// This is an enum parse_statement_decoration_t
node_offset_t statement_idx = (is_decorated ? 1 : 0);
parse_node_t *plain_statement = tree->get_child(node, statement_idx, symbol_plain_statement);
if (plain_statement != NULL)
{
plain_statement->tag = static_cast<enum parse_statement_decoration_t>(node.production_idx);
}
}
break;
default:
break;
}
}
}
bool parse_t::parse(const wcstring &str, parse_tree_flags_t parse_flags, parse_node_tree_t *output, parse_error_list_t *errors, bool log_it)
{
tok_flags_t tok_options = TOK_SQUASH_ERRORS;
if (parse_flags & parse_flag_include_comments)
tok_options |= TOK_SHOW_COMMENTS;
this->parser->set_should_generate_error_messages(errors != NULL);
tokenizer_t tok = tokenizer_t(str.c_str(), tok_options);
for (; tok_has_next(&tok) && ! this->parser->has_fatal_error(); tok_next(&tok))
@ -835,7 +892,9 @@ bool parse_t::parse(const wcstring &str, parse_tree_flags_t parse_flags, parse_n
// Teach each node where its source range is
this->parser->determine_node_ranges();
// Tag nodes
#if 0
wcstring result = dump_tree(this->parser->nodes, str);
fprintf(stderr, "Tree (%ld nodes):\n%ls", this->parser->nodes.size(), result.c_str());
@ -845,6 +904,9 @@ bool parse_t::parse(const wcstring &str, parse_tree_flags_t parse_flags, parse_n
// Acquire the output from the parser
this->parser->acquire_output(output, errors);
// Set node tags
tag_nodes(str, output);
// Indicate if we had a fatal error
return ! this->parser->has_fatal_error();
}
@ -859,6 +921,9 @@ bool parse_t::parse_1_token(parse_token_type_t token_type, parse_keyword_t keywo
token.keyword = keyword;
token.source_start = -1;
token.source_length = 0;
bool wants_errors = (errors != NULL);
this->parser->set_should_generate_error_messages(wants_errors);
this->parser->accept_token(token);
@ -889,6 +954,14 @@ const parse_node_t *parse_node_tree_t::get_child(const parse_node_t &parent, nod
return result;
}
/* Hackish non-const version of get_child */
parse_node_t *parse_node_tree_t::get_child(const parse_node_t &parent, node_offset_t which, parse_token_type_t expected_type)
{
const parse_node_tree_t *const_this = this;
const parse_node_t *result = const_this->get_child(parent, which, expected_type);
return const_cast<parse_node_t *>(result);
}
static void find_nodes_recursive(const parse_node_tree_t &tree, const parse_node_t &parent, parse_token_type_t type, parse_node_tree_t::parse_node_list_t *result)
{
if (parent.type == type) result->push_back(&parent);

View file

@ -124,7 +124,7 @@ enum
/* 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
};
@ -155,7 +155,7 @@ 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);
/** Base class for nodes of a parse tree */
/** Class for nodes of a parse tree */
class parse_node_t
{
public:
@ -193,24 +193,40 @@ public:
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);
}
/* Indicate if this node's source range contains a given location. The funny math makes this modulo-overflow safe, though overflow is not expected. */
bool source_contains_location(size_t where) const
{
return this->has_source() && where >= source_start && where - 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;
parse_node_t *get_child(const parse_node_t &parent, node_offset_t which, parse_token_type_t expected_type = token_type_invalid);
/* Find all the nodes of a given type underneath a given node */
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) const;
};
/* Statement decorations, stored in the tag of plain_statement. 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
};
/* Fish grammar:
@ -259,9 +275,10 @@ public:
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"
# The tag of a plain statement indicates which mode to use
decorated_statement = COMMAND plain_statement | BUILTIN plain_statement | plain_statement
plain_statement = COMMAND arguments_or_redirections_list optional_background
decorated_statement = plain_statement | COMMAND plain_statement | BUILTIN plain_statement
plain_statement = <TOK_STRING> arguments_or_redirections_list optional_background
arguments_or_redirections_list = <empty> |
argument_or_redirection arguments_or_redirections_list