fish-shell/src/function.cpp

393 lines
14 KiB
C++
Raw Normal View History

// Functions for storing and retrieving function information. These functions also take care of
// autoloading functions in the $fish_function_path. Actual function evaluation is taken care of by
// the parser and to some degree the builtin handling library.
//
#include "config.h" // IWYU pragma: keep
// IWYU pragma: no_include <type_traits>
#include <dirent.h>
#include <pthread.h>
#include <stddef.h>
#include <cwchar>
#include <algorithm>
2012-01-14 15:44:18 +08:00
#include <map>
#include <memory>
2015-07-25 23:14:25 +08:00
#include <string>
#include <unordered_map>
#include <unordered_set>
2015-07-25 23:14:25 +08:00
#include <utility>
2015-07-25 23:14:25 +08:00
#include "autoload.h"
#include "common.h"
#include "env.h"
#include "event.h"
#include "exec.h"
#include "fallback.h" // IWYU pragma: keep
#include "function.h"
#include "intern.h"
2018-09-25 10:26:46 +08:00
#include "parser.h"
#include "parser_keywords.h"
#include "reader.h"
#include "wutil.h" // IWYU pragma: keep
class function_info_t {
public:
/// Immutable properties of the function.
std::shared_ptr<const function_properties_t> props;
/// Function description. This may be changed after the function is created.
wcstring description;
/// File where this function was defined (intern'd string).
const wchar_t *const definition_file;
/// Mapping of all variables that were inherited from the function definition scope to their
/// values.
const std::map<wcstring, env_var_t> inherit_vars;
/// Flag for specifying that this function was automatically loaded.
const bool is_autoload;
/// Constructs relevant information from the function_data.
function_info_t(function_data_t data, const environment_t &vars, const wchar_t *filename,
bool autoload);
/// Used by function_copy.
function_info_t(const function_info_t &data, const wchar_t *filename, bool autoload);
};
/// Type wrapping up the set of all functions.
/// There's only one of these; it's managed by a lock.
struct function_set_t {
/// The map of all functions by name.
std::unordered_map<wcstring, function_info_t> funcs;
/// Tombstones for functions that should no longer be autoloaded.
std::unordered_set<wcstring> autoload_tombstones;
/// The autoloader for our functions.
autoload_t autoloader{L"fish_function_path"};
/// Remove a function.
/// \return true if successful, false if it doesn't exist.
bool remove(const wcstring &name);
/// Get the info for a function, or nullptr if none.
const function_info_t *get_info(const wcstring &name) const {
auto iter = funcs.find(name);
return iter == funcs.end() ? nullptr : &iter->second;
}
/// \return true if we should allow autoloading a given function.
bool allow_autoload(const wcstring &name) const;
function_set_t() = default;
};
/// The big set of all functions.
static owning_lock<function_set_t> function_set;
bool function_set_t::allow_autoload(const wcstring &name) const {
// Prohibit autoloading if we have a non-autoload (explicit) function, or if the function is
// tombstoned.
const auto *info = get_info(name);
bool has_explicit_func = info && !info->is_autoload;
bool is_tombstoned = autoload_tombstones.count(name) > 0;
return !has_explicit_func && !is_tombstoned;
}
/// Make sure that if the specified function is a dynamically loaded function, it has been fully
/// loaded.
/// Note this executes fish script code.
static void try_autoload(const wcstring &name, parser_t &parser) {
ASSERT_IS_MAIN_THREAD();
maybe_t<wcstring> path_to_autoload;
// Note we can't autoload while holding the funcset lock.
// Lock around a local region.
{
auto funcset = function_set.acquire();
if (funcset->allow_autoload(name)) {
path_to_autoload = funcset->autoloader.resolve_command(name, parser.vars());
}
}
// Release the lock and perform any autoload, then reacquire the lock and clean up.
if (path_to_autoload) {
// Crucially, the lock is acquired *after* do_autoload_file_at_path().
autoload_t::perform_autoload(*path_to_autoload, parser);
function_set.acquire()->autoloader.mark_autoload_finished(name);
}
}
/// Insert a list of all dynamically loaded functions into the specified list.
Squashed commit of the following: commit 50f414a45d58fcab664ff662dd27befcfa0fdd95 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:35 2017 -0500 Converted file_id_t set to unordered_set with custom hash commit 83ef2dd7cc1bc3e4fdf0b2d3546d6811326cc3c9 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:14 2017 -0500 Converted remaining set<wcstring> to unordered_set<wcstring> commit 053da88f933f27505b3cf4810402e2a2be070203 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:29:21 2017 -0500 Switched function sets to unordered_set commit d469742a14ac99599022a9258cda8255178826b5 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:21:32 2017 -0500 Converted list of modified variables to an unordered set commit 5c06f866beeafb23878b1a932c7cd2558412c283 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:15:20 2017 -0500 Convert const_string_set_t to std::unordered_set As it is a readonly-list of raw character pointer strings (not wcstring), this necessitated the addition of a hashing function since the C++ standard library does not come with a char pointer hash function. To that end, a zlib-licensed [0] port of the excellent, lightweight XXHash family of 32- and 64-bit hashing algorithms in the form of a C++ header-only include library has been included. XXHash32/64 is pretty much universally the fastest hashing library for general purpose applications, and has been thoroughly vetted and is used in countless open source projects. The single-header version of this library makes it a lot simpler to include in the fish project, and the license compatibility with fish' GPLv2 and the zero-lib nature should make it an easy decision. std::unordered_set brings a massive speedup as compared to the default std::set, and the further use of the fast XXHash library to provide the string hashing should make all forms of string lookups in fish significantly faster (to a user-noticeable extent). 0: http://create.stephan-brumme.com/about.html commit 30d7710be8f0c23a4d42f7e713fcb7850f99036e Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:29:39 2017 -0500 Using std::unordered_set for completions backing store While the completions shown to the user are sorted, their storage in memory does not need to be since they are re-sorted before they are shown in completions.cpp. commit 695e83331d7a60ba188e57f6ea0d9b6da54860c6 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:06:53 2017 -0500 Updated is_loading to use unordered_set
2017-08-20 04:29:52 +08:00
static void autoload_names(std::unordered_set<wcstring> &names, int get_hidden) {
size_t i;
2018-09-25 10:26:46 +08:00
// TODO: justfy this.
auto &vars = env_stack_t::principal();
const auto path_var = vars.get(L"fish_function_path");
if (path_var.missing_or_empty()) return;
wcstring_list_t path_list;
path_var->to_list(path_list);
for (i = 0; i < path_list.size(); i++) {
const wcstring &ndir_str = path_list.at(i);
dir_t dir(ndir_str);
if (!dir.valid()) continue;
wcstring name;
while (dir.read(name)) {
const wchar_t *fn = name.c_str();
const wchar_t *suffix;
if (!get_hidden && fn[0] == L'_') continue;
suffix = std::wcsrchr(fn, L'.');
if (suffix && (std::wcscmp(suffix, L".fish") == 0)) {
2012-01-14 15:44:18 +08:00
wcstring name(fn, suffix - fn);
names.insert(name);
}
}
}
}
2018-09-25 10:26:46 +08:00
static std::map<wcstring, env_var_t> snapshot_vars(const wcstring_list_t &vars,
const environment_t &src) {
std::map<wcstring, env_var_t> result;
for (const wcstring &name : vars) {
2018-09-25 10:26:46 +08:00
auto var = src.get(name);
if (var) result[name] = std::move(*var);
}
return result;
}
2018-09-25 10:26:46 +08:00
function_info_t::function_info_t(function_data_t data, const environment_t &vars,
const wchar_t *filename, bool autoload)
: props(std::make_shared<const function_properties_t>(std::move(data.props))),
description(std::move(data.description)),
definition_file(intern(filename)),
2018-09-25 10:26:46 +08:00
inherit_vars(snapshot_vars(data.inherit_vars, vars)),
is_autoload(autoload) {}
function_info_t::function_info_t(const function_info_t &data, const wchar_t *filename,
bool autoload)
: props(data.props),
description(data.description),
definition_file(intern(filename)),
inherit_vars(data.inherit_vars),
is_autoload(autoload) {}
void function_add(const function_data_t &data, const parser_t &parser) {
ASSERT_IS_MAIN_THREAD();
auto funcset = function_set.acquire();
// Historical check. TODO: rationalize this.
if (data.name.empty()) {
return;
}
// Remove the old function.
funcset->remove(data.name);
// Check if this is a function that we are autoloading.
bool is_autoload = funcset->autoloader.autoload_in_progress(data.name);
// Create and store a new function.
const wchar_t *filename = parser.libdata().current_filename;
auto ins = funcset->funcs.emplace(data.name,
function_info_t(data, parser.vars(), filename, is_autoload));
assert(ins.second && "Function should not already be present in the table");
(void)ins;
// Add event handlers.
for (const event_description_t &ed : data.events) {
event_add_handler(std::make_shared<event_handler_t>(ed, data.name));
}
}
std::shared_ptr<const function_properties_t> function_get_properties(const wcstring &name) {
if (parser_keywords_is_reserved(name)) return nullptr;
auto funcset = function_set.acquire();
if (const auto *info = funcset->get_info(name)) {
return info->props;
}
return nullptr;
}
int function_exists(const wcstring &cmd, parser_t &parser) {
ASSERT_IS_MAIN_THREAD();
if (parser_keywords_is_reserved(cmd)) return 0;
try_autoload(cmd, parser);
auto funcset = function_set.acquire();
return funcset->funcs.find(cmd) != funcset->funcs.end();
}
void function_load(const wcstring &cmd, parser_t &parser) {
ASSERT_IS_MAIN_THREAD();
if (!parser_keywords_is_reserved(cmd)) {
try_autoload(cmd, parser);
}
}
int function_exists_no_autoload(const wcstring &cmd, const environment_t &vars) {
2019-05-13 09:23:00 +08:00
(void)vars;
if (parser_keywords_is_reserved(cmd)) return 0;
auto funcset = function_set.acquire();
// Check if we either have the function, or it could be autoloaded.
return funcset->get_info(cmd) || funcset->autoloader.can_autoload(cmd);
2012-01-26 10:40:08 +08:00
}
bool function_set_t::remove(const wcstring &name) {
size_t amt = funcs.erase(name);
if (amt > 0) {
event_remove_function_handlers(name);
}
return amt > 0;
}
void function_remove(const wcstring &name) {
auto funcset = function_set.acquire();
funcset->remove(name);
// Prevent (re-)autoloading this function.
funcset->autoload_tombstones.insert(name);
}
bool function_get_definition(const wcstring &name, wcstring &out_definition) {
const auto funcset = function_set.acquire();
if (const function_info_t *func = funcset->get_info(name)) {
auto props = func->props;
if (props && props->parsed_source) {
out_definition = props->body_node.get_source(props->parsed_source->src);
}
return true;
}
return false;
}
std::map<wcstring, env_var_t> function_get_inherit_vars(const wcstring &name) {
const auto funcset = function_set.acquire();
const function_info_t *func = funcset->get_info(name);
return func ? func->inherit_vars : std::map<wcstring, env_var_t>();
}
bool function_get_desc(const wcstring &name, wcstring &out_desc) {
const auto funcset = function_set.acquire();
const function_info_t *func = funcset->get_info(name);
if (func && !func->description.empty()) {
out_desc = _(func->description.c_str());
return true;
}
2016-10-29 08:42:50 +08:00
return false;
}
void function_set_desc(const wcstring &name, const wcstring &desc, parser_t &parser) {
ASSERT_IS_MAIN_THREAD();
try_autoload(name, parser);
auto funcset = function_set.acquire();
auto iter = funcset->funcs.find(name);
if (iter != funcset->funcs.end()) {
iter->second.description = desc;
}
}
bool function_copy(const wcstring &name, const wcstring &new_name) {
auto funcset = function_set.acquire();
auto iter = funcset->funcs.find(name);
if (iter == funcset->funcs.end()) {
// No such function.
return false;
2012-01-14 15:44:18 +08:00
}
// This new instance of the function shouldn't be tied to the definition file of the
// original, so pass NULL filename, etc.
// Note this will NOT overwrite an existing function with the new name.
// TODO: rationalize if this behavior is desired.
funcset->funcs.emplace(new_name, function_info_t(iter->second, nullptr, false));
return true;
}
wcstring_list_t function_get_names(int get_hidden) {
Squashed commit of the following: commit 50f414a45d58fcab664ff662dd27befcfa0fdd95 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:35 2017 -0500 Converted file_id_t set to unordered_set with custom hash commit 83ef2dd7cc1bc3e4fdf0b2d3546d6811326cc3c9 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:14 2017 -0500 Converted remaining set<wcstring> to unordered_set<wcstring> commit 053da88f933f27505b3cf4810402e2a2be070203 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:29:21 2017 -0500 Switched function sets to unordered_set commit d469742a14ac99599022a9258cda8255178826b5 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:21:32 2017 -0500 Converted list of modified variables to an unordered set commit 5c06f866beeafb23878b1a932c7cd2558412c283 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:15:20 2017 -0500 Convert const_string_set_t to std::unordered_set As it is a readonly-list of raw character pointer strings (not wcstring), this necessitated the addition of a hashing function since the C++ standard library does not come with a char pointer hash function. To that end, a zlib-licensed [0] port of the excellent, lightweight XXHash family of 32- and 64-bit hashing algorithms in the form of a C++ header-only include library has been included. XXHash32/64 is pretty much universally the fastest hashing library for general purpose applications, and has been thoroughly vetted and is used in countless open source projects. The single-header version of this library makes it a lot simpler to include in the fish project, and the license compatibility with fish' GPLv2 and the zero-lib nature should make it an easy decision. std::unordered_set brings a massive speedup as compared to the default std::set, and the further use of the fast XXHash library to provide the string hashing should make all forms of string lookups in fish significantly faster (to a user-noticeable extent). 0: http://create.stephan-brumme.com/about.html commit 30d7710be8f0c23a4d42f7e713fcb7850f99036e Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:29:39 2017 -0500 Using std::unordered_set for completions backing store While the completions shown to the user are sorted, their storage in memory does not need to be since they are re-sorted before they are shown in completions.cpp. commit 695e83331d7a60ba188e57f6ea0d9b6da54860c6 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:06:53 2017 -0500 Updated is_loading to use unordered_set
2017-08-20 04:29:52 +08:00
std::unordered_set<wcstring> names;
auto funcset = function_set.acquire();
autoload_names(names, get_hidden);
for (const auto &func : funcset->funcs) {
Squashed commit of the following: commit 50f414a45d58fcab664ff662dd27befcfa0fdd95 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:35 2017 -0500 Converted file_id_t set to unordered_set with custom hash commit 83ef2dd7cc1bc3e4fdf0b2d3546d6811326cc3c9 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:14 2017 -0500 Converted remaining set<wcstring> to unordered_set<wcstring> commit 053da88f933f27505b3cf4810402e2a2be070203 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:29:21 2017 -0500 Switched function sets to unordered_set commit d469742a14ac99599022a9258cda8255178826b5 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:21:32 2017 -0500 Converted list of modified variables to an unordered set commit 5c06f866beeafb23878b1a932c7cd2558412c283 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:15:20 2017 -0500 Convert const_string_set_t to std::unordered_set As it is a readonly-list of raw character pointer strings (not wcstring), this necessitated the addition of a hashing function since the C++ standard library does not come with a char pointer hash function. To that end, a zlib-licensed [0] port of the excellent, lightweight XXHash family of 32- and 64-bit hashing algorithms in the form of a C++ header-only include library has been included. XXHash32/64 is pretty much universally the fastest hashing library for general purpose applications, and has been thoroughly vetted and is used in countless open source projects. The single-header version of this library makes it a lot simpler to include in the fish project, and the license compatibility with fish' GPLv2 and the zero-lib nature should make it an easy decision. std::unordered_set brings a massive speedup as compared to the default std::set, and the further use of the fast XXHash library to provide the string hashing should make all forms of string lookups in fish significantly faster (to a user-noticeable extent). 0: http://create.stephan-brumme.com/about.html commit 30d7710be8f0c23a4d42f7e713fcb7850f99036e Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:29:39 2017 -0500 Using std::unordered_set for completions backing store While the completions shown to the user are sorted, their storage in memory does not need to be since they are re-sorted before they are shown in completions.cpp. commit 695e83331d7a60ba188e57f6ea0d9b6da54860c6 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:06:53 2017 -0500 Updated is_loading to use unordered_set
2017-08-20 04:29:52 +08:00
const wcstring &name = func.first;
// Maybe skip hidden.
if (!get_hidden && (name.empty() || name.at(0) == L'_')) {
continue;
2012-01-14 15:44:18 +08:00
}
names.insert(name);
}
return wcstring_list_t(names.begin(), names.end());
}
const wchar_t *function_get_definition_file(const wcstring &name) {
const auto funcset = function_set.acquire();
const function_info_t *func = funcset->get_info(name);
return func ? func->definition_file : NULL;
}
bool function_is_autoloaded(const wcstring &name) {
const auto funcset = function_set.acquire();
const function_info_t *func = funcset->get_info(name);
return func ? func->is_autoload : false;
}
int function_get_definition_lineno(const wcstring &name) {
const auto funcset = function_set.acquire();
const function_info_t *func = funcset->get_info(name);
if (!func) return -1;
// return one plus the number of newlines at offsets less than the start of our function's
// statement (which includes the header).
// TODO: merge with line_offset_of_character_at_offset?
auto block_stat = func->props->body_node.try_get_parent<grammar::block_statement>();
assert(block_stat && "Function body is not part of block statement");
auto source_range = block_stat.source_range();
assert(source_range && "Function has no source range");
uint32_t func_start = source_range->start;
const wcstring &source = func->props->parsed_source->src;
assert(func_start <= source.size() && "function start out of bounds");
return 1 + std::count(source.begin(), source.begin() + func_start, L'\n');
}
void function_invalidate_path() {
// Remove all autoloaded functions and update the autoload path.
// Note we don't want to risk removal during iteration; we expect this to be called
// infrequently.
auto funcset = function_set.acquire();
wcstring_list_t autoloadees;
for (const auto &kv : funcset->funcs) {
if (kv.second.is_autoload) {
autoloadees.push_back(kv.first);
}
}
for (const wcstring &name : autoloadees) {
funcset->remove(name);
}
funcset->autoloader.clear();
}
// Setup the environment for the function. There are three components of the environment:
// 1. argv
// 2. named arguments
// 3. inherited variables
void function_prepare_environment(env_stack_t &vars, const wcstring &name, wcstring_list_t argv,
const std::map<wcstring, env_var_t> &inherited_vars) {
2018-09-11 10:17:44 +08:00
vars.set_argv(argv);
auto props = function_get_properties(name);
if (props && !props->named_arguments.empty()) {
auto argv_iter = argv.cbegin();
for (const wcstring &named_arg : props->named_arguments) {
if (argv_iter != argv.cend()) {
vars.set_one(named_arg, ENV_LOCAL | ENV_USER, std::move(*argv_iter));
++argv_iter;
} else {
2018-09-11 12:27:25 +08:00
vars.set_empty(named_arg, ENV_LOCAL | ENV_USER);
}
}
}
for (const auto &kv : inherited_vars) {
vars.set(kv.first, ENV_LOCAL | ENV_USER, kv.second.as_list());
}
}