fish-shell/src/parse_constants.h
ridiculousfish f11a60473a Introduce expansion limits
This adds the ability to limit how many expansions are produced. For
example if $big contains 10 items, and is Cartesian-expanded as
$big$big$big$big... 10 times, we would naviely get 10^10 = 10 billion
results, which fish can't actually handle. Implement this in
completion_receiver_t, which now can return false to indicate an overflow.

The initial expansion limit 'k_default_expansion_limit' is set as 512k
items. There's no way for users to change this at present.
2020-12-05 13:19:07 -08:00

282 lines
11 KiB
C++

// Constants used in the programmatic representation of fish code.
#ifndef FISH_PARSE_CONSTANTS_H
#define FISH_PARSE_CONSTANTS_H
#include "config.h"
#include "common.h"
#define PARSER_DIE() \
do { \
FLOG(error, L"Parser dying!"); \
exit_without_destructors(-1); \
} while (0)
// A range of source code.
struct source_range_t {
uint32_t start;
uint32_t length;
uint32_t end() const {
assert(start + length >= start && "Overflow");
return start + length;
}
};
// IMPORTANT: If the following enum table is modified you must also update token_enum_map below.
enum class parse_token_type_t : uint8_t {
invalid = 1,
// Terminal types.
string,
pipe,
redirection,
background,
andand,
oror,
end,
// Special terminal type that means no more tokens forthcoming.
terminate,
// Very special terminal types that don't appear in the production list.
error,
tokenizer_error,
comment,
};
const enum_map<parse_token_type_t> token_enum_map[] = {
{parse_token_type_t::comment, L"parse_token_type_t::comment"},
{parse_token_type_t::error, L"parse_token_type_t::error"},
{parse_token_type_t::tokenizer_error, L"parse_token_type_t::tokenizer_error"},
{parse_token_type_t::background, L"parse_token_type_t::background"},
{parse_token_type_t::end, L"parse_token_type_t::end"},
{parse_token_type_t::pipe, L"parse_token_type_t::pipe"},
{parse_token_type_t::redirection, L"parse_token_type_t::redirection"},
{parse_token_type_t::string, L"parse_token_type_t::string"},
{parse_token_type_t::andand, L"parse_token_type_t::andand"},
{parse_token_type_t::oror, L"parse_token_type_t::oror"},
{parse_token_type_t::terminate, L"parse_token_type_t::terminate"},
{parse_token_type_t::invalid, L"parse_token_type_t::invalid"},
{parse_token_type_t::invalid, nullptr}};
#define token_enum_map_len (sizeof token_enum_map / sizeof *token_enum_map)
// IMPORTANT: If the following enum is modified you must update the corresponding keyword_enum_map
// array below.
//
// IMPORTANT: These enums must start at zero.
enum class parse_keyword_t : uint8_t {
// 'none' is not a keyword, it is a sentinel indicating nothing.
none,
kw_and,
kw_begin,
kw_builtin,
kw_case,
kw_command,
kw_else,
kw_end,
kw_exclam,
kw_exec,
kw_for,
kw_function,
kw_if,
kw_in,
kw_not,
kw_or,
kw_switch,
kw_time,
kw_while,
};
const enum_map<parse_keyword_t> keyword_enum_map[] = {{parse_keyword_t::kw_exclam, L"!"},
{parse_keyword_t::kw_and, L"and"},
{parse_keyword_t::kw_begin, L"begin"},
{parse_keyword_t::kw_builtin, L"builtin"},
{parse_keyword_t::kw_case, L"case"},
{parse_keyword_t::kw_command, L"command"},
{parse_keyword_t::kw_else, L"else"},
{parse_keyword_t::kw_end, L"end"},
{parse_keyword_t::kw_exec, L"exec"},
{parse_keyword_t::kw_for, L"for"},
{parse_keyword_t::kw_function, L"function"},
{parse_keyword_t::kw_if, L"if"},
{parse_keyword_t::kw_in, L"in"},
{parse_keyword_t::kw_not, L"not"},
{parse_keyword_t::kw_or, L"or"},
{parse_keyword_t::kw_switch, L"switch"},
{parse_keyword_t::kw_time, L"time"},
{parse_keyword_t::kw_while, L"while"},
{parse_keyword_t::none, nullptr}};
#define keyword_enum_map_len (sizeof keyword_enum_map / sizeof *keyword_enum_map)
// Statement decorations like 'command' or 'exec'.
enum class statement_decoration_t {
none,
command,
builtin,
exec,
};
// 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
// Tokenizer errors.
parse_error_tokenizer_unterminated_quote,
parse_error_tokenizer_unterminated_subshell,
parse_error_tokenizer_unterminated_slice,
parse_error_tokenizer_unterminated_escape,
parse_error_tokenizer_other,
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_bare_variable_assignment, // a=b without command
parse_error_andor_in_pipeline, // "and" or "or" after a pipe
};
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,
/// Indicate that the parser should generate job_list entries for blank lines.
parse_flag_show_blank_lines = 1 << 4,
/// Indicate that extra semis should be generated.
parse_flag_show_extra_semis = 1 << 5,
};
typedef unsigned int parse_tree_flags_t;
enum { PARSER_TEST_ERROR = 1, PARSER_TEST_INCOMPLETE = 2 };
typedef unsigned int parser_test_error_bits_t;
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
/// is_interactive is true, the offending line with a caret is printed as well.
wcstring describe(const wcstring &src, bool is_interactive) const;
/// Return a string describing the error, suitable for presentation to the user, with the given
/// prefix. If skip_caret is false, the offending line with a caret is printed as well.
wcstring describe_with_prefix(const wcstring &src, const wcstring &prefix, bool is_interactive,
bool skip_caret) const;
};
typedef std::vector<parse_error_t> parse_error_list_t;
wcstring token_type_user_presentable_description(parse_token_type_t type,
parse_keyword_t keyword = parse_keyword_t::none);
// Special source_start value that means unknown.
#define SOURCE_LOCATION_UNKNOWN (static_cast<size_t>(-1))
/// Helper function to offset error positions by the given amount. This is used when determining
/// errors in a substring of a larger source buffer.
void parse_error_offset_source_start(parse_error_list_t *errors, size_t amt);
// The location of a pipeline.
enum class pipeline_position_t {
none, // not part of a pipeline
first, // first command in a pipeline
subsequent // second or further command in a pipeline
};
/// 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 " \
L"loop?")
/// Error message when encountering an illegal command name.
#define ILLEGAL_CMD_ERR_MSG _(L"Illegal command name '%ls'")
/// Error message when encountering an unknown builtin name.
#define UNKNOWN_BUILTIN_ERR_MSG _(L"Unknown builtin '%ls'")
/// Error message when encountering a failed expansion, e.g. for the variable name in for loops.
#define FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG _(L"Unable to expand variable 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'. See `help expand`.")
/// Error message when an expansion produces too many results, e.g. `echo /**`.
#define EXPAND_OVERFLOW_ERR_MSG _(L"Too many items produced by '%ls'.")
/// Error when using break outside of loop.
#define INVALID_BREAK_ERR_MSG _(L"'break' while not inside of loop")
/// Error when using continue outside of loop.
#define INVALID_CONTINUE_ERR_MSG _(L"'continue' while not inside of loop")
/// Error when using return builtin outside of function definition.
#define INVALID_RETURN_ERR_MSG _(L"'return' outside of function definition")
// Error messages. The number is a reminder of how many format specifiers are contained.
/// Error for $^.
#define ERROR_BAD_VAR_CHAR1 _(L"$%lc is not a valid variable in fish.")
/// Error for ${a}.
#define ERROR_BRACKETED_VARIABLE1 _(L"Variables cannot be bracketed. In fish, please use {$%ls}.")
/// Error for "${a}".
#define ERROR_BRACKETED_VARIABLE_QUOTED1 \
_(L"Variables cannot be bracketed. In fish, please use \"$%ls\".")
/// Error issued on $?.
#define ERROR_NOT_STATUS _(L"$? is not the exit status. In fish, please use $status.")
/// Error issued on $$.
#define ERROR_NOT_PID _(L"$$ is not the pid. In fish, please use $fish_pid.")
/// Error issued on $#.
#define ERROR_NOT_ARGV_COUNT _(L"$# is not supported. In fish, please use 'count $argv'.")
/// Error issued on $@.
#define ERROR_NOT_ARGV_AT _(L"$@ is not supported. In fish, please use $argv.")
/// Error issued on $(...).
#define ERROR_BAD_VAR_SUBCOMMAND1 _(L"$(...) is not supported. In fish, please use '(%ls)'.")
/// Error issued on $*.
#define ERROR_NOT_ARGV_STAR _(L"$* is not supported. In fish, please use $argv.")
/// Error issued on $.
#define ERROR_NO_VAR_NAME _(L"Expected a variable name after this $.")
/// Error message for Posix-style assignment: foo=bar.
#define ERROR_BAD_COMMAND_ASSIGN_ERR_MSG \
_(L"Unsupported use of '='. In fish, please use 'set %ls %ls'.")
/// Error message for a command like `time foo &`.
#define ERROR_TIME_BACKGROUND \
_(L"'time' is not supported for background jobs. Consider using 'command time'.")
#endif