2016-05-03 03:31:33 +08:00
|
|
|
// Constants used in the programmatic representation of fish code.
|
2016-04-21 14:00:54 +08:00
|
|
|
#ifndef FISH_PARSE_CONSTANTS_H
|
|
|
|
#define FISH_PARSE_CONSTANTS_H
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-11-10 13:37:49 +08:00
|
|
|
#include "common.h"
|
2014-03-26 11:13:33 +08:00
|
|
|
#include "config.h"
|
|
|
|
|
2013-12-09 13:54:06 +08:00
|
|
|
#define PARSE_ASSERT(a) assert(a)
|
2017-01-03 13:11:53 +08:00
|
|
|
#define PARSER_DIE() \
|
|
|
|
do { \
|
|
|
|
debug(0, "Parser dying!"); \
|
|
|
|
exit_without_destructors(-1); \
|
2016-05-03 03:31:33 +08:00
|
|
|
} while (0)
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-11-10 13:37:49 +08:00
|
|
|
// IMPORTANT: If the following enum table is modified you must also update token_enum_map below.
|
2016-05-03 03:31:33 +08:00
|
|
|
enum parse_token_type_t {
|
2016-11-10 13:37:49 +08:00
|
|
|
token_type_invalid = 1,
|
2013-12-09 13:54:06 +08:00
|
|
|
// Non-terminal tokens
|
|
|
|
symbol_job_list,
|
2018-03-02 05:39:39 +08:00
|
|
|
symbol_job_conjunction,
|
2013-12-09 13:54:06 +08:00
|
|
|
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,
|
2015-12-20 06:45:45 +08:00
|
|
|
symbol_andor_job_list,
|
2013-12-09 13:54:06 +08:00
|
|
|
symbol_argument_list,
|
2016-11-10 13:37:49 +08:00
|
|
|
// Freestanding argument lists are parsed from the argument list supplied to 'complete -a'.
|
2016-05-03 03:31:33 +08:00
|
|
|
// They are not generated by parse trees rooted in symbol_job_list.
|
2014-03-28 02:17:05 +08:00
|
|
|
symbol_freestanding_argument_list,
|
2013-12-09 13:54:06 +08:00
|
|
|
symbol_argument,
|
|
|
|
symbol_redirection,
|
|
|
|
symbol_optional_background,
|
2018-02-19 06:37:44 +08:00
|
|
|
symbol_optional_newlines,
|
2013-12-12 10:34:28 +08:00
|
|
|
symbol_end_command,
|
2016-05-03 03:31:33 +08:00
|
|
|
// Terminal types.
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_token_type_string,
|
|
|
|
parse_token_type_pipe,
|
|
|
|
parse_token_type_redirection,
|
|
|
|
parse_token_type_background,
|
2018-03-02 05:39:39 +08:00
|
|
|
parse_token_type_andand,
|
|
|
|
parse_token_type_oror,
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_token_type_end,
|
2016-05-03 03:31:33 +08:00
|
|
|
// Special terminal type that means no more tokens forthcoming.
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_token_type_terminate,
|
2016-05-03 03:31:33 +08:00
|
|
|
// Very special terminal types that don't appear in the production list.
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_special_type_parse_error,
|
|
|
|
parse_special_type_tokenizer_error,
|
|
|
|
parse_special_type_comment,
|
|
|
|
|
2016-11-10 13:37:49 +08:00
|
|
|
LAST_TOKEN_TYPE = parse_special_type_comment,
|
2013-12-09 13:54:06 +08:00
|
|
|
FIRST_TERMINAL_TYPE = parse_token_type_string,
|
|
|
|
LAST_TERMINAL_TYPE = parse_token_type_terminate,
|
|
|
|
LAST_TOKEN_OR_SYMBOL = parse_token_type_terminate,
|
2014-12-24 07:29:42 +08:00
|
|
|
FIRST_PARSE_TOKEN_TYPE = parse_token_type_string,
|
|
|
|
LAST_PARSE_TOKEN_TYPE = parse_token_type_end
|
2014-03-26 11:06:34 +08:00
|
|
|
} __packed;
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-11-10 13:37:49 +08:00
|
|
|
const enum_map<parse_token_type_t> token_enum_map[] = {
|
|
|
|
{parse_special_type_comment, L"parse_special_type_comment"},
|
|
|
|
{parse_special_type_parse_error, L"parse_special_type_parse_error"},
|
|
|
|
{parse_special_type_tokenizer_error, L"parse_special_type_tokenizer_error"},
|
|
|
|
{parse_token_type_background, L"parse_token_type_background"},
|
|
|
|
{parse_token_type_end, L"parse_token_type_end"},
|
|
|
|
{parse_token_type_pipe, L"parse_token_type_pipe"},
|
|
|
|
{parse_token_type_redirection, L"parse_token_type_redirection"},
|
|
|
|
{parse_token_type_string, L"parse_token_type_string"},
|
2018-03-02 05:39:39 +08:00
|
|
|
{parse_token_type_andand, L"parse_token_type_andand"},
|
|
|
|
{parse_token_type_oror, L"parse_token_type_oror"},
|
2016-11-10 13:37:49 +08:00
|
|
|
{parse_token_type_terminate, L"parse_token_type_terminate"},
|
|
|
|
{symbol_andor_job_list, L"symbol_andor_job_list"},
|
|
|
|
{symbol_argument, L"symbol_argument"},
|
|
|
|
{symbol_argument_list, L"symbol_argument_list"},
|
|
|
|
{symbol_arguments_or_redirections_list, L"symbol_arguments_or_redirections_list"},
|
|
|
|
{symbol_begin_header, L"symbol_begin_header"},
|
|
|
|
{symbol_block_header, L"symbol_block_header"},
|
|
|
|
{symbol_block_statement, L"symbol_block_statement"},
|
|
|
|
{symbol_boolean_statement, L"symbol_boolean_statement"},
|
|
|
|
{symbol_case_item, L"symbol_case_item"},
|
|
|
|
{symbol_case_item_list, L"symbol_case_item_list"},
|
|
|
|
{symbol_decorated_statement, L"symbol_decorated_statement"},
|
|
|
|
{symbol_else_clause, L"symbol_else_clause"},
|
|
|
|
{symbol_else_continuation, L"symbol_else_continuation"},
|
|
|
|
{symbol_end_command, L"symbol_end_command"},
|
|
|
|
{symbol_for_header, L"symbol_for_header"},
|
|
|
|
{symbol_freestanding_argument_list, L"symbol_freestanding_argument_list"},
|
|
|
|
{symbol_function_header, L"symbol_function_header"},
|
|
|
|
{symbol_if_clause, L"symbol_if_clause"},
|
|
|
|
{symbol_if_statement, L"symbol_if_statement"},
|
|
|
|
{symbol_job, L"symbol_job"},
|
2018-03-02 05:39:39 +08:00
|
|
|
{symbol_job_conjunction, L"symbol_job_conjunction"},
|
2016-11-10 13:37:49 +08:00
|
|
|
{symbol_job_continuation, L"symbol_job_continuation"},
|
|
|
|
{symbol_job_list, L"symbol_job_list"},
|
2018-02-19 06:37:44 +08:00
|
|
|
{symbol_optional_newlines, L"symbol_optional_newlines"},
|
2016-11-10 13:37:49 +08:00
|
|
|
{symbol_optional_background, L"symbol_optional_background"},
|
|
|
|
{symbol_plain_statement, L"symbol_plain_statement"},
|
|
|
|
{symbol_redirection, L"symbol_redirection"},
|
|
|
|
{symbol_statement, L"symbol_statement"},
|
|
|
|
{symbol_switch_statement, L"symbol_switch_statement"},
|
|
|
|
{symbol_while_header, L"symbol_while_header"},
|
|
|
|
{token_type_invalid, L"token_type_invalid"},
|
|
|
|
{token_type_invalid, NULL}};
|
|
|
|
#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.
|
2016-04-10 09:56:13 +08:00
|
|
|
//
|
2016-11-10 13:37:49 +08:00
|
|
|
// IMPORTANT: These enums must start at zero.
|
2016-05-03 03:31:33 +08:00
|
|
|
enum parse_keyword_t {
|
2016-11-10 13:37:49 +08:00
|
|
|
parse_keyword_none = 0,
|
2014-10-16 03:04:23 +08:00
|
|
|
parse_keyword_and,
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_keyword_begin,
|
2014-10-16 03:04:23 +08:00
|
|
|
parse_keyword_builtin,
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_keyword_case,
|
|
|
|
parse_keyword_command,
|
2014-10-16 03:04:23 +08:00
|
|
|
parse_keyword_else,
|
|
|
|
parse_keyword_end,
|
2014-02-14 02:08:04 +08:00
|
|
|
parse_keyword_exec,
|
2014-10-16 03:04:23 +08:00
|
|
|
parse_keyword_for,
|
|
|
|
parse_keyword_function,
|
|
|
|
parse_keyword_if,
|
|
|
|
parse_keyword_in,
|
|
|
|
parse_keyword_not,
|
|
|
|
parse_keyword_or,
|
|
|
|
parse_keyword_switch,
|
|
|
|
parse_keyword_while,
|
2014-03-26 11:06:34 +08:00
|
|
|
} __packed;
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-11-10 13:37:49 +08:00
|
|
|
const enum_map<parse_keyword_t> keyword_enum_map[] = {
|
2016-12-04 12:12:53 +08:00
|
|
|
{parse_keyword_and, L"and"}, {parse_keyword_begin, L"begin"},
|
|
|
|
{parse_keyword_builtin, L"builtin"}, {parse_keyword_case, L"case"},
|
|
|
|
{parse_keyword_command, L"command"}, {parse_keyword_else, L"else"},
|
|
|
|
{parse_keyword_end, L"end"}, {parse_keyword_exec, L"exec"},
|
|
|
|
{parse_keyword_for, L"for"}, {parse_keyword_function, L"function"},
|
|
|
|
{parse_keyword_if, L"if"}, {parse_keyword_in, L"in"},
|
|
|
|
{parse_keyword_not, L"not"}, {parse_keyword_or, L"or"},
|
|
|
|
{parse_keyword_switch, L"switch"}, {parse_keyword_while, L"while"},
|
2016-11-10 13:37:49 +08:00
|
|
|
{parse_keyword_none, NULL}};
|
|
|
|
#define keyword_enum_map_len (sizeof keyword_enum_map / sizeof *keyword_enum_map)
|
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Node tag values.
|
2015-12-16 06:59:03 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Statement decorations, stored in node tag.
|
|
|
|
enum parse_statement_decoration_t {
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_statement_decoration_none,
|
|
|
|
parse_statement_decoration_command,
|
2014-02-14 02:08:04 +08:00
|
|
|
parse_statement_decoration_builtin,
|
|
|
|
parse_statement_decoration_exec
|
2013-12-09 13:54:06 +08:00
|
|
|
};
|
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Boolean statement types, stored in node tag.
|
|
|
|
enum parse_bool_statement_type_t { parse_bool_and, parse_bool_or, parse_bool_not };
|
2014-11-03 05:11:27 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Whether a statement is backgrounded.
|
|
|
|
enum parse_optional_background_t { parse_no_background, parse_background };
|
2015-12-16 06:59:03 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Parse error code list.
|
|
|
|
enum parse_error_code_t {
|
2013-12-09 13:54:06 +08:00
|
|
|
parse_error_none,
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Matching values from enum parser_error.
|
2013-12-13 10:18:07 +08:00
|
|
|
parse_error_syntax,
|
|
|
|
parse_error_eval,
|
|
|
|
parse_error_cmdsubst,
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
parse_error_generic, // unclassified error types
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Tokenizer errors.
|
2014-01-14 16:01:26 +08:00
|
|
|
parse_error_tokenizer_unterminated_quote,
|
|
|
|
parse_error_tokenizer_unterminated_subshell,
|
2015-08-11 10:30:21 +08:00
|
|
|
parse_error_tokenizer_unterminated_slice,
|
2014-01-14 16:01:26 +08:00
|
|
|
parse_error_tokenizer_unterminated_escape,
|
|
|
|
parse_error_tokenizer_other,
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
parse_error_unbalancing_end, // end outside of block
|
|
|
|
parse_error_unbalancing_else, // else outside of if
|
2018-03-02 03:45:41 +08:00
|
|
|
parse_error_unbalancing_case // case outside of switch
|
2013-12-09 13:54:06 +08:00
|
|
|
};
|
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
enum { PARSER_TEST_ERROR = 1, PARSER_TEST_INCOMPLETE = 2 };
|
2013-12-16 08:05:37 +08:00
|
|
|
typedef unsigned int parser_test_error_bits_t;
|
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
struct parse_error_t {
|
|
|
|
/// Text of the error.
|
2014-03-22 08:13:33 +08:00
|
|
|
wcstring text;
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Code for the error.
|
2014-03-22 08:13:33 +08:00
|
|
|
enum parse_error_code_t code;
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Offset and length of the token in the source code that triggered this error.
|
2014-03-22 08:13:33 +08:00
|
|
|
size_t source_start;
|
|
|
|
size_t source_length;
|
2016-05-03 03:31:33 +08:00
|
|
|
/// 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.
|
2014-03-22 08:13:33 +08:00
|
|
|
wcstring describe(const wcstring &src) const;
|
2016-05-03 03:31:33 +08:00
|
|
|
/// 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;
|
2014-03-22 08:13:33 +08:00
|
|
|
};
|
|
|
|
typedef std::vector<parse_error_t> parse_error_list_t;
|
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Special source_start value that means unknown.
|
2014-03-22 08:13:33 +08:00
|
|
|
#define SOURCE_LOCATION_UNKNOWN (static_cast<size_t>(-1))
|
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Helper function to offset error positions by the given amount. This is used when determining
|
|
|
|
/// errors in a substring of a larger source buffer.
|
2014-03-22 08:13:33 +08:00
|
|
|
void parse_error_offset_source_start(parse_error_list_t *errors, size_t amt);
|
2013-12-16 08:05:37 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Maximum number of function calls.
|
2014-01-02 07:29:56 +08:00
|
|
|
#define FISH_MAX_STACK_DEPTH 128
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// 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.")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// 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?")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error message when encountering an illegal command name.
|
|
|
|
#define ILLEGAL_CMD_ERR_MSG _(L"Illegal command name '%ls'")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error message when encountering an unknown builtin name.
|
|
|
|
#define UNKNOWN_BUILTIN_ERR_MSG _(L"Unknown builtin '%ls'")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// 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'")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error message when encountering a failed process expansion, e.g. %notaprocess.
|
|
|
|
#define FAILED_EXPANSION_PROCESS_ERR_MSG _(L"Unable to find a process '%ls'")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error message when encountering an illegal file descriptor.
|
|
|
|
#define ILLEGAL_FD_ERR_MSG _(L"Illegal file descriptor in redirection '%ls'")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error message for wildcards with no matches.
|
2017-03-26 20:38:59 +08:00
|
|
|
#define WILDCARD_ERR_MSG _(L"No matches for wildcard '%ls'. See `help expand`.")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error when using break outside of loop.
|
|
|
|
#define INVALID_BREAK_ERR_MSG _(L"'break' while not inside of loop")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error when using continue outside of loop.
|
|
|
|
#define INVALID_CONTINUE_ERR_MSG _(L"'continue' while not inside of loop")
|
2015-04-30 07:53:02 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error when using return builtin outside of function definition.
|
|
|
|
#define INVALID_RETURN_ERR_MSG _(L"'return' outside of function definition")
|
2014-03-04 18:53:34 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
// Error messages. The number is a reminder of how many format specifiers are contained.
|
2014-03-04 18:53:34 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error for $^.
|
|
|
|
#define ERROR_BAD_VAR_CHAR1 _(L"$%lc is not a valid variable in fish.")
|
2014-03-04 18:53:34 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error for ${a}.
|
|
|
|
#define ERROR_BRACKETED_VARIABLE1 _(L"Variables cannot be bracketed. In fish, please use {$%ls}.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error for "${a}".
|
|
|
|
#define ERROR_BRACKETED_VARIABLE_QUOTED1 \
|
|
|
|
_(L"Variables cannot be bracketed. In fish, please use \"$%ls\".")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error issued on $?.
|
|
|
|
#define ERROR_NOT_STATUS _(L"$? is not the exit status. In fish, please use $status.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error issued on $$.
|
|
|
|
#define ERROR_NOT_PID _(L"$$ is not the pid. In fish, please use %%self.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error issued on $#.
|
|
|
|
#define ERROR_NOT_ARGV_COUNT _(L"$# is not supported. In fish, please use 'count $argv'.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error issued on $@.
|
|
|
|
#define ERROR_NOT_ARGV_AT _(L"$@ is not supported. In fish, please use $argv.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error issued on $(...).
|
|
|
|
#define ERROR_BAD_VAR_SUBCOMMAND1 _(L"$(...) is not supported. In fish, please use '(%ls)'.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error issued on $*.
|
|
|
|
#define ERROR_NOT_ARGV_STAR _(L"$* is not supported. In fish, please use $argv.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error issued on $.
|
|
|
|
#define ERROR_NO_VAR_NAME _(L"Expected a variable name after this $.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// Error on foo=bar.
|
|
|
|
#define ERROR_BAD_EQUALS_IN_COMMAND5 \
|
|
|
|
_(L"Unsupported use of '='. To run '%ls' with a modified environment, please use 'env " \
|
|
|
|
L"%ls=%ls %ls%ls'")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
2016-05-03 03:31:33 +08:00
|
|
|
/// 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'.")
|
2013-12-09 13:54:06 +08:00
|
|
|
|
|
|
|
#endif
|