aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/function.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/function.cpp')
-rw-r--r--src/function.cpp396
1 files changed, 396 insertions, 0 deletions
diff --git a/src/function.cpp b/src/function.cpp
new file mode 100644
index 00000000..fb8dbf6a
--- /dev/null
+++ b/src/function.cpp
@@ -0,0 +1,396 @@
+/** \file function.c
+
+ Prototypes for 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"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <termios.h>
+#include <signal.h>
+#include <pthread.h>
+#include <errno.h>
+#include <map>
+#include <set>
+
+#include "wutil.h"
+#include "fallback.h"
+#include "util.h"
+
+#include "function.h"
+#include "proc.h"
+#include "parser.h"
+#include "common.h"
+#include "intern.h"
+#include "event.h"
+#include "reader.h"
+#include "parse_util.h"
+#include "parser_keywords.h"
+#include "env.h"
+#include "expand.h"
+
+/**
+ Table containing all functions
+*/
+typedef std::map<wcstring, function_info_t> function_map_t;
+static function_map_t loaded_functions;
+
+/**
+ Functions that shouldn't be autoloaded (anymore).
+*/
+static std::set<wcstring> function_tombstones;
+
+/* Lock for functions */
+static pthread_mutex_t functions_lock;
+
+/* Autoloader for functions */
+class function_autoload_t : public autoload_t
+{
+public:
+ function_autoload_t();
+ virtual void command_removed(const wcstring &cmd);
+};
+
+static function_autoload_t function_autoloader;
+
+/** Constructor */
+function_autoload_t::function_autoload_t() : autoload_t(L"fish_function_path", NULL, 0)
+{
+}
+
+static bool function_remove_ignore_autoload(const wcstring &name, bool tombstone = true);
+
+/** Callback when an autoloaded function is removed */
+void function_autoload_t::command_removed(const wcstring &cmd)
+{
+ function_remove_ignore_autoload(cmd, false);
+}
+
+/**
+ Kludgy flag set by the load function in order to tell function_add
+ that the function being defined is autoloaded. There should be a
+ better way to do this...
+*/
+static bool is_autoload = false;
+
+/**
+ Make sure that if the specified function is a dynamically loaded
+ function, it has been fully loaded.
+*/
+static int load(const wcstring &name)
+{
+ ASSERT_IS_MAIN_THREAD();
+ scoped_lock lock(functions_lock);
+ bool was_autoload = is_autoload;
+ int res;
+
+ bool no_more_autoload = function_tombstones.count(name) > 0;
+ if (no_more_autoload)
+ return 0;
+
+ function_map_t::iterator iter = loaded_functions.find(name);
+ if (iter != loaded_functions.end() && !iter->second.is_autoload)
+ {
+ /* We have a non-autoload version already */
+ return 0;
+ }
+
+ is_autoload = true;
+ res = function_autoloader.load(name, true);
+ is_autoload = was_autoload;
+ return res;
+}
+
+/**
+ Insert a list of all dynamically loaded functions into the
+ specified list.
+*/
+static void autoload_names(std::set<wcstring> &names, int get_hidden)
+{
+ size_t i;
+
+ const env_var_t path_var_wstr = env_get_string(L"fish_function_path");
+ if (path_var_wstr.missing())
+ return;
+ const wchar_t *path_var = path_var_wstr.c_str();
+
+ wcstring_list_t path_list;
+
+ tokenize_variable_array(path_var, path_list);
+ for (i=0; i<path_list.size(); i++)
+ {
+ const wcstring &ndir_str = path_list.at(i);
+ const wchar_t *ndir = (wchar_t *)ndir_str.c_str();
+ DIR *dir = wopendir(ndir);
+ if (!dir)
+ continue;
+
+ wcstring name;
+ while (wreaddir(dir, name))
+ {
+ const wchar_t *fn = name.c_str();
+ const wchar_t *suffix;
+ if (!get_hidden && fn[0] == L'_')
+ continue;
+
+ suffix = wcsrchr(fn, L'.');
+ if (suffix && (wcscmp(suffix, L".fish") == 0))
+ {
+ wcstring name(fn, suffix - fn);
+ names.insert(name);
+ }
+ }
+ closedir(dir);
+ }
+}
+
+void function_init()
+{
+ /* PCA: This recursive lock was introduced early in my work. I would like to make this a non-recursive lock but I haven't fully investigated all the call paths (for autoloading functions, etc.) */
+ pthread_mutexattr_t a;
+ VOMIT_ON_FAILURE(pthread_mutexattr_init(&a));
+ VOMIT_ON_FAILURE(pthread_mutexattr_settype(&a,PTHREAD_MUTEX_RECURSIVE));
+ VOMIT_ON_FAILURE(pthread_mutex_init(&functions_lock, &a));
+ VOMIT_ON_FAILURE(pthread_mutexattr_destroy(&a));
+}
+
+static std::map<wcstring,env_var_t> snapshot_vars(const wcstring_list_t &vars)
+{
+ std::map<wcstring,env_var_t> result;
+ for (wcstring_list_t::const_iterator it = vars.begin(), end = vars.end(); it != end; ++it)
+ {
+ result.insert(std::make_pair(*it, env_get_string(*it)));
+ }
+ return result;
+}
+
+function_info_t::function_info_t(const function_data_t &data, const wchar_t *filename, int def_offset, bool autoload) :
+ definition(data.definition),
+ description(data.description),
+ definition_file(intern(filename)),
+ definition_offset(def_offset),
+ named_arguments(data.named_arguments),
+ inherit_vars(snapshot_vars(data.inherit_vars)),
+ is_autoload(autoload),
+ shadows(data.shadows)
+{
+}
+
+function_info_t::function_info_t(const function_info_t &data, const wchar_t *filename, int def_offset, bool autoload) :
+ definition(data.definition),
+ description(data.description),
+ definition_file(intern(filename)),
+ definition_offset(def_offset),
+ named_arguments(data.named_arguments),
+ inherit_vars(data.inherit_vars),
+ is_autoload(autoload),
+ shadows(data.shadows)
+{
+}
+
+void function_add(const function_data_t &data, const parser_t &parser, int definition_line_offset)
+{
+ ASSERT_IS_MAIN_THREAD();
+
+ CHECK(! data.name.empty(),);
+ CHECK(data.definition,);
+ scoped_lock lock(functions_lock);
+
+ /* Remove the old function */
+ function_remove(data.name);
+
+ /* Create and store a new function */
+ const wchar_t *filename = reader_current_filename();
+
+ const function_map_t::value_type new_pair(data.name, function_info_t(data, filename, definition_line_offset, is_autoload));
+ loaded_functions.insert(new_pair);
+
+ /* Add event handlers */
+ for (std::vector<event_t>::const_iterator iter = data.events.begin(); iter != data.events.end(); ++iter)
+ {
+ event_add_handler(*iter);
+ }
+}
+
+int function_exists(const wcstring &cmd)
+{
+ if (parser_keywords_is_reserved(cmd))
+ return 0;
+ scoped_lock lock(functions_lock);
+ load(cmd);
+ return loaded_functions.find(cmd) != loaded_functions.end();
+}
+
+int function_exists_no_autoload(const wcstring &cmd, const env_vars_snapshot_t &vars)
+{
+ if (parser_keywords_is_reserved(cmd))
+ return 0;
+ scoped_lock lock(functions_lock);
+ return loaded_functions.find(cmd) != loaded_functions.end() || function_autoloader.can_load(cmd, vars);
+}
+
+static bool function_remove_ignore_autoload(const wcstring &name, bool tombstone)
+{
+ // Note: the lock may be held at this point, but is recursive
+ scoped_lock lock(functions_lock);
+
+ function_map_t::iterator iter = loaded_functions.find(name);
+
+ // not found. not erasing.
+ if (iter == loaded_functions.end())
+ return false;
+
+ // removing an auto-loaded function. prevent it from being
+ // auto-reloaded.
+ if (iter->second.is_autoload && tombstone)
+ function_tombstones.insert(name);
+
+ loaded_functions.erase(iter);
+
+ event_t ev(EVENT_ANY);
+ ev.function_name=name;
+ event_remove(ev);
+
+ return true;
+}
+
+void function_remove(const wcstring &name)
+{
+ if (function_remove_ignore_autoload(name))
+ function_autoloader.unload(name);
+}
+
+static const function_info_t *function_get(const wcstring &name)
+{
+ // The caller must lock the functions_lock before calling this; however our mutex is currently recursive, so trylock will never fail
+ // We need a way to correctly check if a lock is locked (or better yet, make our lock non-recursive)
+ //ASSERT_IS_LOCKED(functions_lock);
+ function_map_t::iterator iter = loaded_functions.find(name);
+ if (iter == loaded_functions.end())
+ {
+ return NULL;
+ }
+ else
+ {
+ return &iter->second;
+ }
+}
+
+bool function_get_definition(const wcstring &name, wcstring *out_definition)
+{
+ scoped_lock lock(functions_lock);
+ const function_info_t *func = function_get(name);
+ if (func && out_definition)
+ {
+ out_definition->assign(func->definition);
+ }
+ return func != NULL;
+}
+
+wcstring_list_t function_get_named_arguments(const wcstring &name)
+{
+ scoped_lock lock(functions_lock);
+ const function_info_t *func = function_get(name);
+ return func ? func->named_arguments : wcstring_list_t();
+}
+
+std::map<wcstring,env_var_t> function_get_inherit_vars(const wcstring &name)
+{
+ scoped_lock lock(functions_lock);
+ const function_info_t *func = function_get(name);
+ return func ? func->inherit_vars : std::map<wcstring,env_var_t>();
+}
+
+int function_get_shadows(const wcstring &name)
+{
+ scoped_lock lock(functions_lock);
+ const function_info_t *func = function_get(name);
+ return func ? func->shadows : false;
+}
+
+
+bool function_get_desc(const wcstring &name, wcstring *out_desc)
+{
+ /* Empty length string goes to NULL */
+ scoped_lock lock(functions_lock);
+ const function_info_t *func = function_get(name);
+ if (out_desc && func && ! func->description.empty())
+ {
+ out_desc->assign(_(func->description.c_str()));
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void function_set_desc(const wcstring &name, const wcstring &desc)
+{
+ load(name);
+ scoped_lock lock(functions_lock);
+ function_map_t::iterator iter = loaded_functions.find(name);
+ if (iter != loaded_functions.end())
+ {
+ iter->second.description = desc;
+ }
+}
+
+bool function_copy(const wcstring &name, const wcstring &new_name)
+{
+ bool result = false;
+ scoped_lock lock(functions_lock);
+ function_map_t::const_iterator iter = loaded_functions.find(name);
+ if (iter != loaded_functions.end())
+ {
+ // This new instance of the function shouldn't be tied to the definition file of the original, so pass NULL filename, etc.
+ const function_map_t::value_type new_pair(new_name, function_info_t(iter->second, NULL, 0, false));
+ loaded_functions.insert(new_pair);
+ result = true;
+ }
+ return result;
+}
+
+wcstring_list_t function_get_names(int get_hidden)
+{
+ std::set<wcstring> names;
+ scoped_lock lock(functions_lock);
+ autoload_names(names, get_hidden);
+
+ function_map_t::const_iterator iter;
+ for (iter = loaded_functions.begin(); iter != loaded_functions.end(); ++iter)
+ {
+ const wcstring &name = iter->first;
+
+ /* Maybe skip hidden */
+ if (! get_hidden)
+ {
+ if (name.empty() || name.at(0) == L'_') continue;
+ }
+ names.insert(name);
+ }
+ return wcstring_list_t(names.begin(), names.end());
+}
+
+const wchar_t *function_get_definition_file(const wcstring &name)
+{
+ scoped_lock lock(functions_lock);
+ const function_info_t *func = function_get(name);
+ return func ? func->definition_file : NULL;
+}
+
+
+int function_get_definition_offset(const wcstring &name)
+{
+ scoped_lock lock(functions_lock);
+ const function_info_t *func = function_get(name);
+ return func ? func->definition_offset : -1;
+}