From 044f5512e2d46250909e81f851779c1ff589eecb Mon Sep 17 00:00:00 2001 From: Kurtis Rader Date: Tue, 13 Jun 2017 12:56:20 -0700 Subject: [PATCH] split builtin function into its own module --- Makefile.in | 1 + src/builtin.cpp | 228 +-------------------------------- src/builtin_function.cpp | 270 +++++++++++++++++++++++++++++++++++++++ src/builtin_function.h | 9 ++ 4 files changed, 281 insertions(+), 227 deletions(-) create mode 100644 src/builtin_function.cpp create mode 100644 src/builtin_function.h diff --git a/Makefile.in b/Makefile.in index e9de54f84..0da541e59 100644 --- a/Makefile.in +++ b/Makefile.in @@ -102,6 +102,7 @@ FISH_OBJS := obj/autoload.o obj/builtin.o obj/builtin_bind.o obj/builtin_block.o obj/builtin_commandline.o obj/builtin_emit.o obj/builtin_functions.o \ obj/builtin_history.o obj/builtin_status.o obj/builtin_read.o \ obj/builtin_random.o \ + obj/builtin_function.o \ obj/builtin_complete.o obj/builtin_jobs.o obj/builtin_printf.o \ obj/builtin_set.o obj/builtin_set_color.o obj/builtin_string.o \ obj/builtin_test.o obj/builtin_ulimit.o obj/color.o obj/common.o \ diff --git a/src/builtin.cpp b/src/builtin.cpp index d16ac04c8..6e5e798c5 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -39,6 +39,7 @@ #include "builtin_commandline.h" #include "builtin_complete.h" #include "builtin_emit.h" +#include "builtin_function.h" #include "builtin_functions.h" #include "builtin_history.h" #include "builtin_jobs.h" @@ -669,233 +670,6 @@ static int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv) return STATUS_CMD_OK; } -static int validate_function_name(int argc, const wchar_t *const *argv, wcstring &function_name, - const wchar_t *cmd, wcstring *out_err) { - if (argc < 2) { - // This is currently impossible but let's be paranoid. - append_format(*out_err, _(L"%ls: Expected function name"), cmd); - return STATUS_INVALID_ARGS; - } - - function_name = argv[1]; - if (!valid_func_name(function_name)) { - append_format(*out_err, _(L"%ls: Illegal function name '%ls'"), cmd, function_name.c_str()); - return STATUS_INVALID_ARGS; - } - - if (parser_keywords_is_reserved(function_name)) { - append_format( - *out_err, - _(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name"), cmd, - function_name.c_str()); - return STATUS_INVALID_ARGS; - } - - return STATUS_CMD_OK; -} - -/// Define a function. Calls into `function.cpp` to perform the heavy lifting of defining a -/// function. -int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_list_t &c_args, - const wcstring &contents, int definition_line_offset, wcstring *out_err) { - assert(out_err != NULL); - - // The wgetopt function expects 'function' as the first argument. Make a new wcstring_list with - // that property. This is needed because this builtin has a different signature than the other - // builtins. - 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 argv_array(args); - wchar_t **argv = const_cast(argv_array.get()); - wchar_t *cmd = argv[0]; - int argc = builtin_count_args(argv); - wchar_t *desc = NULL; - bool shadow_scope = true; - wcstring function_name; - std::vector events; - wcstring_list_t named_arguments; - wcstring_list_t inherit_vars; - wcstring_list_t wrap_targets; - - // A valid function name has to be the first argument. - if (validate_function_name(argc, argv, function_name, cmd, out_err) == STATUS_CMD_OK) { - argv++; - argc--; - } else { - return STATUS_INVALID_ARGS; - } - - // This command is atypical in using the "+" (REQUIRE_ORDER) option for flag parsing. - // This is needed due to the semantics of the -a/--argument-names flag. - static const wchar_t *short_options = L"+:a:d:e:hj:p:s:v:w:SV:"; - static const struct woption long_options[] = { - {L"description", required_argument, NULL, 'd'}, - {L"on-signal", required_argument, NULL, 's'}, - {L"on-job-exit", required_argument, NULL, 'j'}, - {L"on-process-exit", required_argument, NULL, 'p'}, - {L"on-variable", required_argument, NULL, 'v'}, - {L"on-event", required_argument, NULL, 'e'}, - {L"wraps", required_argument, NULL, 'w'}, - {L"help", no_argument, NULL, 'h'}, - {L"argument-names", required_argument, NULL, 'a'}, - {L"no-scope-shadowing", no_argument, NULL, 'S'}, - {L"inherit-variable", required_argument, NULL, 'V'}, - {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 'd': { - desc = w.woptarg; - break; - } - case 's': { - int sig = wcs2sig(w.woptarg); - if (sig == -1) { - append_format(*out_err, _(L"%ls: Unknown signal '%ls'"), cmd, w.woptarg); - return STATUS_INVALID_ARGS; - } - events.push_back(event_t::signal_event(sig)); - break; - } - case 'v': { - if (!valid_var_name(w.woptarg)) { - append_format(*out_err, BUILTIN_ERR_VARNAME, cmd, w.woptarg); - return STATUS_INVALID_ARGS; - } - - events.push_back(event_t::variable_event(w.woptarg)); - break; - } - case 'e': { - events.push_back(event_t::generic_event(w.woptarg)); - break; - } - case 'j': - case 'p': { - pid_t pid; - event_t e(EVENT_ANY); - - if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) { - job_id_t 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"), cmd); - return STATUS_INVALID_ARGS; - } - e.type = EVENT_JOB_ID; - e.param1.job_id = job_id; - } else { - pid = fish_wcstoi(w.woptarg); - if (errno || pid < 0) { - append_format(*out_err, _(L"%ls: Invalid process id '%ls'"), cmd, - w.woptarg); - return STATUS_INVALID_ARGS; - } - - e.type = EVENT_EXIT; - e.param1.pid = (opt == 'j' ? -1 : 1) * abs(pid); - } - events.push_back(e); - break; - } - case 'a': { - named_arguments.push_back(w.woptarg); - break; - } - case 'S': { - shadow_scope = false; - break; - } - case 'w': { - wrap_targets.push_back(w.woptarg); - break; - } - case 'V': { - if (!valid_var_name(w.woptarg)) { - append_format(*out_err, BUILTIN_ERR_VARNAME, cmd, w.woptarg); - return STATUS_INVALID_ARGS; - } - inherit_vars.push_back(w.woptarg); - break; - } - case 'h': { - builtin_print_help(parser, streams, cmd, streams.out); - return STATUS_CMD_OK; - } - case ':': { - streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - case '?': { - builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - if (argc != w.woptind) { - if (named_arguments.size()) { - for (int i = w.woptind; i < argc; i++) { - named_arguments.push_back(argv[i]); - } - } else { - append_format(*out_err, _(L"%ls: Unexpected positional argument '%ls'"), cmd, - argv[w.woptind]); - return STATUS_INVALID_ARGS; - } - } - - // We have what we need to actually define the function. - function_data_t d; - d.name = function_name; - if (desc) d.description = desc; - d.events.swap(events); - d.shadow_scope = shadow_scope; - d.named_arguments.swap(named_arguments); - d.inherit_vars.swap(inherit_vars); - - 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(); - function_add(d, parser, definition_line_offset); - - // Handle wrap targets by creating the appropriate completions. - for (size_t w = 0; w < wrap_targets.size(); w++) { - complete_add_wrapper(function_name, wrap_targets.at(w)); - } - - return STATUS_CMD_OK; -} - /// The exit builtin. Calls reader_exit to exit and returns the value specified. static int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); diff --git a/src/builtin_function.cpp b/src/builtin_function.cpp new file mode 100644 index 000000000..3ccdd5a4f --- /dev/null +++ b/src/builtin_function.cpp @@ -0,0 +1,270 @@ +// Implementation of the function builtin. +#include "config.h" // IWYU pragma: keep + +#include +#include +#include +#include + +#include +#include + +#include "builtin.h" +#include "builtin_function.h" +#include "common.h" +#include "complete.h" +#include "event.h" +#include "fallback.h" // IWYU pragma: keep +#include "function.h" +#include "io.h" +#include "parser.h" +#include "parser_keywords.h" +#include "wgetopt.h" +#include "wutil.h" // IWYU pragma: keep + +struct cmd_opts { + bool print_help = false; + bool shadow_scope = true; + wchar_t *desc = NULL; + std::vector events; + wcstring_list_t named_arguments; + wcstring_list_t inherit_vars; + wcstring_list_t wrap_targets; +}; + +// This command is atypical in using the "+" (REQUIRE_ORDER) option for flag parsing. +// This is needed due to the semantics of the -a/--argument-names flag. +static const wchar_t *short_options = L"+:a:d:e:hj:p:s:v:w:SV:"; +static const struct woption long_options[] = { + {L"description", required_argument, NULL, 'd'}, + {L"on-signal", required_argument, NULL, 's'}, + {L"on-job-exit", required_argument, NULL, 'j'}, + {L"on-process-exit", required_argument, NULL, 'p'}, + {L"on-variable", required_argument, NULL, 'v'}, + {L"on-event", required_argument, NULL, 'e'}, + {L"wraps", required_argument, NULL, 'w'}, + {L"help", no_argument, NULL, 'h'}, + {L"argument-names", required_argument, NULL, 'a'}, + {L"no-scope-shadowing", no_argument, NULL, 'S'}, + {L"inherit-variable", required_argument, NULL, 'V'}, + {NULL, 0, NULL, 0}}; + +static int parse_cmd_opts(struct cmd_opts *opts, int *optind, //!OCLINT(high ncss method) + int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams, + wcstring *out_err) { + 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 'd': { + opts->desc = w.woptarg; + break; + } + case 's': { + int sig = wcs2sig(w.woptarg); + if (sig == -1) { + append_format(*out_err, _(L"%ls: Unknown signal '%ls'"), cmd, w.woptarg); + return STATUS_INVALID_ARGS; + } + opts->events.push_back(event_t::signal_event(sig)); + break; + } + case 'v': { + if (!valid_var_name(w.woptarg)) { + append_format(*out_err, BUILTIN_ERR_VARNAME, cmd, w.woptarg); + return STATUS_INVALID_ARGS; + } + + opts->events.push_back(event_t::variable_event(w.woptarg)); + break; + } + case 'e': { + opts->events.push_back(event_t::generic_event(w.woptarg)); + break; + } + case 'j': + case 'p': { + pid_t pid; + event_t e(EVENT_ANY); + + if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) { + job_id_t 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"), cmd); + return STATUS_INVALID_ARGS; + } + e.type = EVENT_JOB_ID; + e.param1.job_id = job_id; + } else { + pid = fish_wcstoi(w.woptarg); + if (errno || pid < 0) { + append_format(*out_err, _(L"%ls: Invalid process id '%ls'"), cmd, + w.woptarg); + return STATUS_INVALID_ARGS; + } + + e.type = EVENT_EXIT; + e.param1.pid = (opt == 'j' ? -1 : 1) * abs(pid); + } + opts->events.push_back(e); + break; + } + case 'a': { + opts->named_arguments.push_back(w.woptarg); + break; + } + case 'S': { + opts->shadow_scope = false; + break; + } + case 'w': { + opts->wrap_targets.push_back(w.woptarg); + break; + } + case 'V': { + if (!valid_var_name(w.woptarg)) { + append_format(*out_err, BUILTIN_ERR_VARNAME, cmd, w.woptarg); + return STATUS_INVALID_ARGS; + } + opts->inherit_vars.push_back(w.woptarg); + break; + } + case 'h': { + opts->print_help = true; + return STATUS_CMD_OK; + } + case ':': { + streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; + } + case '?': { + builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; + } + default: { + DIE("unexpected retval from wgetopt_long"); + break; + } + } + } + + *optind = w.woptind; + return STATUS_CMD_OK; +} + +static int validate_function_name(int argc, const wchar_t *const *argv, wcstring &function_name, + const wchar_t *cmd, wcstring *out_err) { + if (argc < 2) { + // This is currently impossible but let's be paranoid. + append_format(*out_err, _(L"%ls: Expected function name"), cmd); + return STATUS_INVALID_ARGS; + } + + function_name = argv[1]; + if (!valid_func_name(function_name)) { + append_format(*out_err, _(L"%ls: Illegal function name '%ls'"), cmd, function_name.c_str()); + return STATUS_INVALID_ARGS; + } + + if (parser_keywords_is_reserved(function_name)) { + append_format( + *out_err, + _(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name"), cmd, + function_name.c_str()); + return STATUS_INVALID_ARGS; + } + + return STATUS_CMD_OK; +} + +/// Define a function. Calls into `function.cpp` to perform the heavy lifting of defining a +/// function. +int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_list_t &c_args, + const wcstring &contents, int definition_line_offset, wcstring *out_err) { + assert(out_err != NULL); + + // The wgetopt function expects 'function' as the first argument. Make a new wcstring_list with + // that property. This is needed because this builtin has a different signature than the other + // builtins. + 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 argv_array(args); + wchar_t **argv = const_cast(argv_array.get()); + wchar_t *cmd = argv[0]; + int argc = builtin_count_args(argv); + wcstring function_name; + struct cmd_opts opts; + + // A valid function name has to be the first argument. + int retval = validate_function_name(argc, argv, function_name, cmd, out_err); + if (retval != STATUS_CMD_OK) return retval; + argv++; + argc--; + + int optind; + retval = parse_cmd_opts(&opts, &optind, argc, argv, parser, streams, out_err); + if (retval != STATUS_CMD_OK) return retval; + + if (opts.print_help) { + builtin_print_help(parser, streams, cmd, streams.out); + return STATUS_CMD_OK; + } + + if (argc != optind) { + if (opts.named_arguments.size()) { + for (int i = optind; i < argc; i++) { + opts.named_arguments.push_back(argv[i]); + } + } else { + append_format(*out_err, _(L"%ls: Unexpected positional argument '%ls'"), cmd, + argv[optind]); + return STATUS_INVALID_ARGS; + } + } + + // We have what we need to actually define the function. + function_data_t d; + d.name = function_name; + if (opts.desc) d.description = opts.desc; + d.events.swap(opts.events); + d.shadow_scope = opts.shadow_scope; + d.named_arguments.swap(opts.named_arguments); + d.inherit_vars.swap(opts.inherit_vars); + + 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(); + function_add(d, parser, definition_line_offset); + + // Handle wrap targets by creating the appropriate completions. + for (size_t w = 0; w < opts.wrap_targets.size(); w++) { + complete_add_wrapper(function_name, opts.wrap_targets.at(w)); + } + + return STATUS_CMD_OK; +} diff --git a/src/builtin_function.h b/src/builtin_function.h new file mode 100644 index 000000000..8722eef57 --- /dev/null +++ b/src/builtin_function.h @@ -0,0 +1,9 @@ +// Prototypes for executing builtin_function function. +#ifndef FISH_BUILTIN_FUNCTION_H +#define FISH_BUILTIN_FUNCTION_H + +class parser_t; +struct io_streams_t; + +int builtin_function(parser_t &parser, io_streams_t &streams, wchar_t **argv); +#endif