/// Extract indexes from an argument of the form `var_name[index1 index2...]`. /// /// Inputs: /// indexes: the list to insert the new indexes into /// src: The source string to parse. This must be a var spec of the form "var[...]" /// /// Returns: /// The total number of indexes parsed, or -1 on error. If any indexes were found the `src` string /// is modified to omit the index expression leaving just the var name. static int parse_index(std::vector<long> &indexes, wchar_t *src, int scope, io_streams_t &streams, const environment_t &vars) { wchar_t *p = std::wcschr(src, L'['); if (!p) return 0; // no slices so nothing for us to do *p = L'\0'; // split the var name from the indexes/slices p++; auto var_str = vars.get(src, scope); wcstring_list_t var; if (var_str) var_str->to_list(var); int count = 0; while (*p != L']') { const wchar_t *end; long l_ind = fish_wcstol(p, &end); if (errno > 0) { // ignore errno == -1 meaning the int did not end with a '\0' streams.err.append_format(_(L"%ls: Invalid index starting at '%ls'\n"), L"set", src); return -1; } p = (wchar_t *)end; // Convert negative index to a positive index. if (l_ind < 0) l_ind = var.size() + l_ind + 1; if (*p == L'.' && *(p + 1) == L'.') { p += 2; long l_ind2 = fish_wcstol(p, &end); if (errno > 0) { // ignore errno == -1 meaning the int did not end with a '\0' return -1; } p = (wchar_t *)end; // Convert negative index to a positive index. if (l_ind2 < 0) l_ind2 = var.size() + l_ind2 + 1; int direction = l_ind2 < l_ind ? -1 : 1; for (long jjj = l_ind; jjj * direction <= l_ind2 * direction; jjj += direction) { indexes.push_back(jjj); count++; } } else { indexes.push_back(l_ind); count++; } } return count; }
void internal_exec(job_t *j, const io_chain_t &&all_ios) { // Do a regular launch - but without forking first... // setup_child_process makes sure signals are properly set up. // PCA This is for handling exec. Passing all_ios here matches what fish 2.0.0 and 1.x did. // It's known to be wrong - for example, it means that redirections bound for subsequent // commands in the pipeline will apply to exec. However, using exec in a pipeline doesn't // really make sense, so I'm not trying to fix it here. if (!setup_child_process(0, all_ios)) { // Decrement SHLVL as we're removing ourselves from the shell "stack". auto shlvl_var = env_get(L"SHLVL", ENV_GLOBAL | ENV_EXPORT); wcstring shlvl_str = L"0"; if (shlvl_var) { long shlvl = fish_wcstol(shlvl_var->as_string().c_str()); if (!errno && shlvl > 0) { shlvl_str = to_string<long>(shlvl - 1); } } env_set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, shlvl_str); // launch_process _never_ returns. launch_process_nofork(j->processes.front().get()); } else { j->set_flag(job_flag_t::CONSTRUCTED, true); j->processes.front()->completed = 1; return; } }
static int handle_flag_s(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, options_t *opts) { if (opts->start_valid) { opts->start = fish_wcstol(w.woptarg); if (opts->start == 0 || opts->start == LONG_MIN || errno == ERANGE) { string_error(streams, _(L"%ls: Invalid start value '%ls'\n"), argv[0], w.woptarg); return STATUS_INVALID_ARGS; } else if (errno) { string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); return STATUS_INVALID_ARGS; } return STATUS_CMD_OK; } string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_INVALID_ARGS; }
// Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being // set. void update_wait_on_escape_ms(const environment_t &vars) { auto escape_time_ms = vars.get(L"fish_escape_delay_ms"); if (escape_time_ms.missing_or_empty()) { wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT; return; } long tmp = fish_wcstol(escape_time_ms->as_string().c_str()); if (errno || tmp < 10 || tmp >= 5000) { std::fwprintf(stderr, L"ignoring fish_escape_delay_ms: value '%ls' " L"is not an integer or is < 10 or >= 5000 ms\n", escape_time_ms->as_string().c_str()); } else { wait_on_escape_ms = (int)tmp; } }
// Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being // set. void update_wait_on_escape_ms() { env_var_t escape_time_ms = env_get_string(L"fish_escape_delay_ms"); if (escape_time_ms.missing_or_empty()) { wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT; return; } long tmp = fish_wcstol(escape_time_ms.c_str()); if (errno || tmp < 10 || tmp >= 5000) { fwprintf(stderr, L"ignoring fish_escape_delay_ms: value '%ls' " L"is not an integer or is < 10 or >= 5000 ms\n", escape_time_ms.c_str()); } else { wait_on_escape_ms = (int)tmp; } }
/// The ulimit builtin, used for setting resource limits. int builtin_ulimit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); bool report_all = false; bool hard = false; bool soft = false; int what = RLIMIT_FSIZE; const wchar_t *short_options = L":HSacdflmnstuvh"; const struct woption long_options[] = {{L"all", no_argument, NULL, 'a'}, {L"hard", no_argument, NULL, 'H'}, {L"soft", no_argument, NULL, 'S'}, {L"core-size", no_argument, NULL, 'c'}, {L"data-size", no_argument, NULL, 'd'}, {L"file-size", no_argument, NULL, 'f'}, {L"lock-size", no_argument, NULL, 'l'}, {L"resident-set-size", no_argument, NULL, 'm'}, {L"file-descriptor-count", no_argument, NULL, 'n'}, {L"stack-size", no_argument, NULL, 's'}, {L"cpu-time", no_argument, NULL, 't'}, {L"process-count", no_argument, NULL, 'u'}, {L"virtual-memory-size", no_argument, NULL, 'v'}, {L"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'a': { report_all = true; break; } case 'H': { hard = true; break; } case 'S': { soft = true; break; } case 'c': { what = RLIMIT_CORE; break; } case 'd': { what = RLIMIT_DATA; break; } case 'f': { what = RLIMIT_FSIZE; break; } #ifdef RLIMIT_MEMLOCK case 'l': { what = RLIMIT_MEMLOCK; break; } #endif #ifdef RLIMIT_RSS case 'm': { what = RLIMIT_RSS; break; } #endif case 'n': { what = RLIMIT_NOFILE; break; } case 's': { what = RLIMIT_STACK; break; } case 't': { what = RLIMIT_CPU; break; } #ifdef RLIMIT_NPROC case 'u': { what = RLIMIT_NPROC; break; } #endif #ifdef RLIMIT_AS case 'v': { what = RLIMIT_AS; break; } #endif case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return 0; } case ':': { streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (report_all) { print_all(hard, streams); return STATUS_BUILTIN_OK; } int arg_count = argc - w.woptind; if (arg_count == 0) { // Show current limit value. print(what, hard, streams); return STATUS_BUILTIN_OK; } else if (arg_count != 1) { streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_BUILTIN_ERROR; } // Change current limit value. if (!hard && !soft) { // Set both hard and soft limits if neither was specified. hard = soft = true; } rlim_t new_limit; if (*argv[w.woptind] == L'\0') { streams.err.append_format(_(L"%ls: New limit cannot be an empty string\n"), cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_BUILTIN_ERROR; } else if (wcscasecmp(argv[w.woptind], L"unlimited") == 0) { new_limit = RLIM_INFINITY; } else if (wcscasecmp(argv[w.woptind], L"hard") == 0) { new_limit = get(what, 1); } else if (wcscasecmp(argv[w.woptind], L"soft") == 0) { new_limit = get(what, soft); } else { new_limit = fish_wcstol(argv[w.woptind]); if (errno) { streams.err.append_format(_(L"%ls: Invalid limit '%ls'\n"), cmd, argv[w.woptind]); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_BUILTIN_ERROR; } new_limit *= get_multiplier(what); } return set_limit(what, hard, soft, new_limit, streams); }
/// The commandline builtin. It is used for specifying a new value for the commandline. int builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; int buffer_part = 0; int cut_at_cursor = 0; int argc = builtin_count_args(argv); int append_mode = 0; int function_mode = 0; int selection_mode = 0; int tokenize = 0; int cursor_mode = 0; int line_mode = 0; int search_mode = 0; int paging_mode = 0; const wchar_t *begin = NULL, *end = NULL; scoped_push<const wchar_t *> saved_current_buffer(¤t_buffer); scoped_push<size_t> saved_current_cursor_pos(¤t_cursor_pos); wcstring transient_commandline; if (get_top_transient(&transient_commandline)) { current_buffer = transient_commandline.c_str(); current_cursor_pos = transient_commandline.size(); } else { current_buffer = reader_get_buffer(); current_cursor_pos = reader_get_cursor_pos(); } if (!get_buffer()) { if (is_interactive_session) { // Prompt change requested while we don't have a prompt, most probably while reading the // init files. Just ignore it. return 1; } streams.err.append(argv[0]); streams.err.append(L": Can not set commandline in non-interactive mode\n"); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } w.woptind = 0; while (1) { static const struct woption long_options[] = {{L"append", no_argument, 0, 'a'}, {L"insert", no_argument, 0, 'i'}, {L"replace", no_argument, 0, 'r'}, {L"current-job", no_argument, 0, 'j'}, {L"current-process", no_argument, 0, 'p'}, {L"current-token", no_argument, 0, 't'}, {L"current-buffer", no_argument, 0, 'b'}, {L"cut-at-cursor", no_argument, 0, 'c'}, {L"function", no_argument, 0, 'f'}, {L"tokenize", no_argument, 0, 'o'}, {L"help", no_argument, 0, 'h'}, {L"input", required_argument, 0, 'I'}, {L"cursor", no_argument, 0, 'C'}, {L"line", no_argument, 0, 'L'}, {L"search-mode", no_argument, 0, 'S'}, {L"selection", no_argument, 0, 's'}, {L"paging-mode", no_argument, 0, 'P'}, {0, 0, 0, 0}}; int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"abijpctwforhI:CLSsP", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } case L'a': { append_mode = APPEND_MODE; break; } case L'b': { buffer_part = STRING_MODE; break; } case L'i': { append_mode = INSERT_MODE; break; } case L'r': { append_mode = REPLACE_MODE; break; } case 'c': { cut_at_cursor = 1; break; } case 't': { buffer_part = TOKEN_MODE; break; } case 'j': { buffer_part = JOB_MODE; break; } case 'p': { buffer_part = PROCESS_MODE; break; } case 'f': { function_mode = 1; break; } case 'o': { tokenize = 1; break; } case 'I': { current_buffer = w.woptarg; current_cursor_pos = wcslen(w.woptarg); break; } case 'C': { cursor_mode = 1; break; } case 'L': { line_mode = 1; break; } case 'S': { search_mode = 1; break; } case 's': { selection_mode = 1; break; } case 'P': { paging_mode = 1; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return 0; } case L'?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return 1; } default: { DIE("unexpected opt"); break; } } } if (function_mode) { int i; // Check for invalid switch combinations. if (buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode || search_mode || paging_mode) { streams.err.append_format(BUILTIN_ERR_COMBO, argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } if (argc == w.woptind) { streams.err.append_format(BUILTIN_ERR_MISSING, argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } for (i = w.woptind; i < argc; i++) { wchar_t c = input_function_get_code(argv[i]); if (c != INPUT_CODE_NONE) { // input_unreadch inserts the specified keypress or readline function at the back of // the queue of unused keypresses. input_queue_ch(c); } else { streams.err.append_format(_(L"%ls: Unknown input function '%ls'\n"), argv[0], argv[i]); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } } return 0; } if (selection_mode) { size_t start, len; const wchar_t *buffer = reader_get_buffer(); if (reader_get_selection(&start, &len)) { streams.out.append(buffer + start, len); } return 0; } // Check for invalid switch combinations. if ((search_mode || line_mode || cursor_mode || paging_mode) && (argc - w.woptind > 1)) { streams.err.append_format(argv[0], L": Too many arguments\n", NULL); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } if ((buffer_part || tokenize || cut_at_cursor) && (cursor_mode || line_mode || search_mode || paging_mode)) { streams.err.append_format(BUILTIN_ERR_COMBO, argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } if ((tokenize || cut_at_cursor) && (argc - w.woptind)) { streams.err.append_format( BUILTIN_ERR_COMBO2, argv[0], L"--cut-at-cursor and --tokenize can not be used when setting the commandline"); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } if (append_mode && !(argc - w.woptind)) { streams.err.append_format( BUILTIN_ERR_COMBO2, argv[0], L"insertion mode switches can not be used when not in insertion mode"); builtin_print_help(parser, streams, argv[0], streams.err); return 1; } // Set default modes. if (!append_mode) { append_mode = REPLACE_MODE; } if (!buffer_part) { buffer_part = STRING_MODE; } if (cursor_mode) { if (argc - w.woptind) { long new_pos = fish_wcstol(argv[w.woptind]); if (errno) { streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, argv[0], argv[w.woptind]); builtin_print_help(parser, streams, argv[0], streams.err); } current_buffer = reader_get_buffer(); new_pos = maxi(0L, mini(new_pos, (long)wcslen(current_buffer))); reader_set_buffer(current_buffer, (size_t)new_pos); } else { streams.out.append_format(L"%lu\n", (unsigned long)reader_get_cursor_pos()); } return 0; } if (line_mode) { size_t pos = reader_get_cursor_pos(); const wchar_t *buff = reader_get_buffer(); streams.out.append_format(L"%lu\n", (unsigned long)parse_util_lineno(buff, pos)); return 0; } if (search_mode) { return !reader_search_mode(); } if (paging_mode) { return !reader_has_pager_contents(); } switch (buffer_part) { case STRING_MODE: { begin = get_buffer(); end = begin + wcslen(begin); break; } case PROCESS_MODE: { parse_util_process_extent(get_buffer(), get_cursor_pos(), &begin, &end); break; } case JOB_MODE: { parse_util_job_extent(get_buffer(), get_cursor_pos(), &begin, &end); break; } case TOKEN_MODE: { parse_util_token_extent(get_buffer(), get_cursor_pos(), &begin, &end, 0, 0); break; } default: { DIE("unexpected buffer_part"); break; } } int arg_count = argc - w.woptind; if (arg_count == 0) { write_part(begin, end, cut_at_cursor, tokenize, streams); } else if (arg_count == 1) { replace_part(begin, end, argv[w.woptind], append_mode); } else { wcstring sb = argv[w.woptind]; for (int i = w.woptind + 1; i < argc; i++) { sb.push_back(L'\n'); sb.append(argv[i]); } replace_part(begin, end, sb.c_str(), append_mode); } return 0; }
static int parse_cmd_opts(history_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method) int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { wchar_t *cmd = argv[0]; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 1: { if (!set_hist_cmd(cmd, &opts.hist_cmd, HIST_DELETE, streams)) { return STATUS_CMD_ERROR; } break; } case 2: { if (!set_hist_cmd(cmd, &opts.hist_cmd, HIST_SEARCH, streams)) { return STATUS_CMD_ERROR; } break; } case 3: { if (!set_hist_cmd(cmd, &opts.hist_cmd, HIST_SAVE, streams)) { return STATUS_CMD_ERROR; } break; } case 4: { if (!set_hist_cmd(cmd, &opts.hist_cmd, HIST_CLEAR, streams)) { return STATUS_CMD_ERROR; } break; } case 5: { if (!set_hist_cmd(cmd, &opts.hist_cmd, HIST_MERGE, streams)) { return STATUS_CMD_ERROR; } break; } case 'C': { opts.case_sensitive = true; break; } case 'p': { opts.search_type = HISTORY_SEARCH_TYPE_PREFIX; opts.history_search_type_defined = true; break; } case 'c': { opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS; opts.history_search_type_defined = true; break; } case 'e': { opts.search_type = HISTORY_SEARCH_TYPE_EXACT; opts.history_search_type_defined = true; break; } case 't': { opts.show_time_format = w.woptarg ? w.woptarg : L"# %c%n"; break; } case 'n': { opts.max_items = fish_wcstol(w.woptarg); if (errno) { streams.err.append_format(_(L"%ls: max value '%ls' is not a valid number\n"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } break; } case 'z': { opts.null_terminate = true; break; } case 'h': { opts.print_help = true; break; } case ':': { streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { // Try to parse it as a number; e.g., "-123". opts.max_items = fish_wcstol(argv[w.woptind - 1] + 1); if (errno) { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } w.nextchar = NULL; break; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } *optind = w.woptind; return STATUS_CMD_OK; }
static int string_sub(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { const wchar_t *short_options = L":l:qs:"; const struct woption long_options[] = {{L"length", required_argument, 0, 'l'}, {L"quiet", no_argument, 0, 'q'}, {L"start", required_argument, 0, 's'}, {0, 0, 0, 0}}; long start = 0; long length = -1; bool quiet = false; wgetopter_t w; for (;;) { int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); if (c == -1) { break; } switch (c) { case 0: { break; } case 'l': { length = fish_wcstol(w.woptarg); if (length < 0 || errno == ERANGE) { string_error(streams, _(L"%ls: Invalid length value '%ls'\n"), argv[0], w.woptarg); return BUILTIN_STRING_ERROR; } else if (errno) { string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); return BUILTIN_STRING_ERROR; } break; } case 'q': { quiet = true; break; } case 's': { start = fish_wcstol(w.woptarg); if (start == 0 || start == LONG_MIN || errno == ERANGE) { string_error(streams, _(L"%ls: Invalid start value '%ls'\n"), argv[0], w.woptarg); return BUILTIN_STRING_ERROR; } else if (errno) { string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); return BUILTIN_STRING_ERROR; } break; } case ':': { string_error(streams, STRING_ERR_MISSING, argv[0]); return BUILTIN_STRING_ERROR; } case '?': { string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } default: { DIE("unexpected opt"); break; } } } int i = w.woptind; if (string_args_from_stdin(streams) && argc > i) { string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); return BUILTIN_STRING_ERROR; } int nsub = 0; const wchar_t *arg; wcstring storage; while ((arg = string_get_arg(&i, argv, &storage, streams)) != NULL) { typedef wcstring::size_type size_type; size_type pos = 0; size_type count = wcstring::npos; wcstring s(arg); if (start > 0) { pos = static_cast<size_type>(start - 1); } else if (start < 0) { assert(start != LONG_MIN); // checked above size_type n = static_cast<size_type>(-start); pos = n > s.length() ? 0 : s.length() - n; } if (pos > s.length()) { pos = s.length(); } if (length >= 0) { count = static_cast<size_type>(length); } // Note that std::string permits count to extend past end of string. if (!quiet) { streams.out.append(s.substr(pos, count)); streams.out.append(L'\n'); } nsub++; } return nsub > 0 ? BUILTIN_STRING_OK : BUILTIN_STRING_NONE; }
static int string_split(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { const wchar_t *short_options = L":m:qr"; const struct woption long_options[] = {{L"max", required_argument, 0, 'm'}, {L"quiet", no_argument, 0, 'q'}, {L"right", no_argument, 0, 'r'}, {0, 0, 0, 0}}; long max = LONG_MAX; bool quiet = false; bool right = false; wgetopter_t w; for (;;) { int c = w.wgetopt_long(argc, argv, short_options, long_options, 0); if (c == -1) { break; } switch (c) { case 0: { break; } case 'm': { max = fish_wcstol(w.woptarg); if (errno) { string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); return BUILTIN_STRING_ERROR; } break; } case 'q': { quiet = true; break; } case 'r': { right = true; break; } case ':': { string_error(streams, STRING_ERR_MISSING, argv[0]); return BUILTIN_STRING_ERROR; } case '?': { string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return BUILTIN_STRING_ERROR; } default: { DIE("unexpected opt"); break; } } } int i = w.woptind; const wchar_t *sep; if ((sep = string_get_arg_argv(&i, argv)) == NULL) { string_error(streams, STRING_ERR_MISSING, argv[0]); return BUILTIN_STRING_ERROR; } const wchar_t *sep_end = sep + wcslen(sep); if (string_args_from_stdin(streams) && argc > i) { string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); return BUILTIN_STRING_ERROR; } wcstring_list_t splits; size_t arg_count = 0; wcstring storage; const wchar_t *arg; while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { const wchar_t *arg_end = arg + wcslen(arg); if (right) { typedef std::reverse_iterator<const wchar_t *> reverser; split_about(reverser(arg_end), reverser(arg), reverser(sep_end), reverser(sep), &splits, max); } else { split_about(arg, arg_end, sep, sep_end, &splits, max); } arg_count++; } // If we are from the right, split_about gave us reversed strings, in reversed order! if (right) { for (size_t j = 0; j < splits.size(); j++) { std::reverse(splits[j].begin(), splits[j].end()); } std::reverse(splits.begin(), splits.end()); } if (!quiet) { for (wcstring_list_t::const_iterator si = splits.begin(); si != splits.end(); ++si) { streams.out.append(*si); streams.out.append(L'\n'); } } // We split something if we have more split values than args. return splits.size() > arg_count ? BUILTIN_STRING_OK : BUILTIN_STRING_NONE; }
/// Extract indexes from a destination argument of the form name[index1 index2...] /// /// \param indexes the list to insert the new indexes into /// \param src the source string to parse /// \param name the name of the element. Return null if the name in \c src does not match this name /// \param var_count the number of elements in the array to parse. /// /// \return the total number of indexes parsed, or -1 on error static int parse_index(std::vector<long> &indexes, const wchar_t *src, const wchar_t *name, size_t var_count, io_streams_t &streams) { size_t len; int count = 0; const wchar_t *src_orig = src; if (src == 0) { return 0; } while (*src != L'\0' && (iswalnum(*src) || *src == L'_')) src++; if (*src != L'[') { streams.err.append_format(_(BUILTIN_SET_ARG_COUNT), L"set"); return 0; } len = src - src_orig; if ((wcsncmp(src_orig, name, len) != 0) || (wcslen(name) != (len))) { streams.err.append_format( _(L"%ls: Multiple variable names specified in single call (%ls and %.*ls)\n"), L"set", name, len, src_orig); return 0; } src++; while (iswspace(*src)) src++; while (*src != L']') { const wchar_t *end; long l_ind = fish_wcstol(src, &end); if (errno > 0) { // ignore errno == -1 meaning the int did not end with a '\0' streams.err.append_format(_(L"%ls: Invalid index starting at '%ls'\n"), L"set", src); return 0; } if (l_ind < 0) l_ind = var_count + l_ind + 1; src = end; //!OCLINT(parameter reassignment) if (*src == L'.' && *(src + 1) == L'.') { src += 2; long l_ind2 = fish_wcstol(src, &end); if (errno > 0) { // ignore errno == -1 meaning the int did not end with a '\0' return 1; } src = end; //!OCLINT(parameter reassignment) if (l_ind2 < 0) { l_ind2 = var_count + l_ind2 + 1; } int direction = l_ind2 < l_ind ? -1 : 1; for (long jjj = l_ind; jjj * direction <= l_ind2 * direction; jjj += direction) { // debug(0, L"Expand range [set]: %i\n", jjj); indexes.push_back(jjj); count++; } } else { indexes.push_back(l_ind); count++; } while (iswspace(*src)) src++; } return count; }
/// Parse an array slicing specification Returns 0 on success. If a parse error occurs, returns the /// index of the bad token. Note that 0 can never be a bad index because the string always starts /// with [. static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &idx, std::vector<size_t> &source_positions, size_t array_size) { const long size = (long)array_size; size_t pos = 1; // skip past the opening square brace while (1) { while (iswspace(in[pos]) || (in[pos] == INTERNAL_SEPARATOR)) pos++; if (in[pos] == L']') { pos++; break; } const size_t i1_src_pos = pos; const wchar_t *end; long tmp = fish_wcstol(&in[pos], &end); // We don't test `*end` as is typically done because we expect it to not be the null char. // Ignore the case of errno==-1 because it means the end char wasn't the null char. if (errno > 0) { return pos; } // debug( 0, L"Push idx %d", tmp ); long i1 = tmp > -1 ? tmp : size + tmp + 1; pos = end - in; while (in[pos] == INTERNAL_SEPARATOR) pos++; if (in[pos] == L'.' && in[pos + 1] == L'.') { pos += 2; while (in[pos] == INTERNAL_SEPARATOR) pos++; const size_t number_start = pos; long tmp1 = fish_wcstol(&in[pos], &end); // Ignore the case of errno==-1 because it means the end char wasn't the null char. if (errno > 0) { return pos; } pos = end - in; // debug( 0, L"Push range %d %d", tmp, tmp1 ); long i2 = tmp1 > -1 ? tmp1 : size + tmp1 + 1; // Clamp to array size, but only when doing a range, // and only when just one is too high. if (i1 > size && i2 > size) { continue; } i1 = i1 < size ? i1 : size; i2 = i2 < size ? i2 : size; // debug( 0, L"Push range idx %d %d", i1, i2 ); short direction = i2 < i1 ? -1 : 1; // If only the beginning is negative, always go reverse. // If only the end, always go forward. // Prevents `[x..-1]` from going reverse if less than x elements are there. if (tmp1 > -1 != tmp > -1) { direction = tmp1 > -1 ? -1 : 1; } for (long jjj = i1; jjj * direction <= i2 * direction; jjj += direction) { // debug(0, L"Expand range [subst]: %i\n", jjj); idx.push_back(jjj); source_positions.push_back(number_start); } continue; } // debug( 0, L"Push idx %d", tmp ); idx.push_back(i1); source_positions.push_back(i1_src_pos); } if (end_ptr) { *end_ptr = (wchar_t *)(in + pos); } return 0; }