2016-05-01 03:31:02 +00:00
|
|
|
// 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.
|
|
|
|
//
|
2016-05-18 22:30:21 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2022-08-21 21:51:33 +00:00
|
|
|
#include "function.h"
|
2022-08-21 06:14:48 +00:00
|
|
|
|
2022-08-21 21:51:33 +00:00
|
|
|
#include <algorithm>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <cstdint>
|
2019-10-13 22:50:48 +00:00
|
|
|
#include <cwchar>
|
2012-01-14 07:44:18 +00:00
|
|
|
#include <map>
|
2016-05-01 03:31:02 +00:00
|
|
|
#include <memory>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <string>
|
2017-08-19 16:55:06 +00:00
|
|
|
#include <unordered_map>
|
2018-02-10 05:53:06 +00:00
|
|
|
#include <unordered_set>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <utility>
|
2022-08-21 06:14:48 +00:00
|
|
|
#include <vector>
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2022-08-21 06:14:48 +00:00
|
|
|
#include "ast.h"
|
2015-07-25 15:14:25 +00:00
|
|
|
#include "autoload.h"
|
2005-09-20 13:26:39 +00:00
|
|
|
#include "common.h"
|
2022-08-21 06:14:48 +00:00
|
|
|
#include "complete.h"
|
2016-05-01 03:31:02 +00:00
|
|
|
#include "env.h"
|
2005-10-05 22:37:08 +00:00
|
|
|
#include "event.h"
|
2016-05-01 03:31:02 +00:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2022-08-21 06:14:48 +00:00
|
|
|
#include "maybe.h"
|
|
|
|
#include "parse_constants.h"
|
2018-09-25 02:26:46 +00:00
|
|
|
#include "parser.h"
|
2007-04-22 09:50:26 +00:00
|
|
|
#include "parser_keywords.h"
|
2023-01-14 22:56:24 +00:00
|
|
|
#include "signals.h"
|
2020-09-21 15:44:58 +00:00
|
|
|
#include "wcstringutil.h"
|
2016-05-01 03:31:02 +00:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2021-09-28 19:50:27 +00:00
|
|
|
namespace {
|
2018-02-11 01:38:57 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
/// 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.
|
2021-10-21 21:17:16 +00:00
|
|
|
std::unordered_map<wcstring, function_properties_ref_t> funcs;
|
2011-12-27 03:18:46 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
/// Tombstones for functions that should no longer be autoloaded.
|
|
|
|
std::unordered_set<wcstring> autoload_tombstones;
|
2015-03-11 13:14:56 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
/// The autoloader for our functions.
|
2019-04-27 22:42:34 +00:00
|
|
|
autoload_t autoloader{L"fish_function_path"};
|
2011-12-27 03:18:46 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
/// Remove a function.
|
|
|
|
/// \return true if successful, false if it doesn't exist.
|
|
|
|
bool remove(const wcstring &name);
|
2017-01-30 02:56:55 +00:00
|
|
|
|
2021-10-21 20:23:49 +00:00
|
|
|
/// Get the properties for a function, or nullptr if none.
|
|
|
|
function_properties_ref_t get_props(const wcstring &name) const {
|
|
|
|
auto iter = funcs.find(name);
|
2021-10-21 21:17:16 +00:00
|
|
|
return iter == funcs.end() ? nullptr : iter->second;
|
2021-10-21 20:23:49 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
/// \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.
|
2021-10-21 20:23:49 +00:00
|
|
|
auto props = get_props(name);
|
|
|
|
bool has_explicit_func = props && !props->is_autoload;
|
2019-04-26 22:03:41 +00:00
|
|
|
bool is_tombstoned = autoload_tombstones.count(name) > 0;
|
|
|
|
return !has_explicit_func && !is_tombstoned;
|
|
|
|
}
|
2021-09-28 19:50:27 +00:00
|
|
|
} // namespace
|
2006-02-08 09:20:05 +00:00
|
|
|
|
2021-10-21 21:17:16 +00:00
|
|
|
/// \return a copy of some function props, in a new shared_ptr.
|
2021-10-31 10:51:16 +00:00
|
|
|
static std::shared_ptr<function_properties_t> copy_props(const function_properties_ref_t &props) {
|
2021-10-21 21:17:16 +00:00
|
|
|
assert(props && "Null props");
|
|
|
|
return std::make_shared<function_properties_t>(*props);
|
|
|
|
}
|
|
|
|
|
2016-05-01 03:31:02 +00:00
|
|
|
/// Make sure that if the specified function is a dynamically loaded function, it has been fully
|
|
|
|
/// loaded.
|
2019-04-26 22:03:41 +00:00
|
|
|
/// Note this executes fish script code.
|
2022-06-11 18:34:52 +00:00
|
|
|
bool function_load(const wcstring &name, parser_t &parser) {
|
2022-06-19 23:27:06 +00:00
|
|
|
parser.assert_can_execute();
|
2019-04-26 22:03:41 +00:00
|
|
|
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)) {
|
2019-07-12 19:19:00 +00:00
|
|
|
path_to_autoload = funcset->autoloader.resolve_command(name, env_stack_t::globals());
|
2019-04-26 22:03:41 +00:00
|
|
|
}
|
2011-12-27 03:18:46 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
// Release the lock and perform any autoload, then reacquire the lock and clean up.
|
|
|
|
if (path_to_autoload) {
|
2022-06-11 18:34:52 +00:00
|
|
|
// Crucially, the lock is acquired after perform_autoload().
|
2019-05-05 03:20:52 +00:00
|
|
|
autoload_t::perform_autoload(*path_to_autoload, parser);
|
2019-04-26 22:03:41 +00:00
|
|
|
function_set.acquire()->autoloader.mark_autoload_finished(name);
|
|
|
|
}
|
2022-06-11 18:34:52 +00:00
|
|
|
return path_to_autoload.has_value();
|
2006-02-08 09:20:05 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 03:31:02 +00:00
|
|
|
/// Insert a list of all dynamically loaded functions into the specified list.
|
2022-06-16 13:48:46 +00:00
|
|
|
static void autoload_names(std::unordered_set<wcstring> &names, bool get_hidden) {
|
2012-11-19 00:30:30 +00:00
|
|
|
size_t i;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2022-10-29 03:07:36 +00:00
|
|
|
// TODO: justify this.
|
2018-09-25 02:26:46 +00:00
|
|
|
auto &vars = env_stack_t::principal();
|
2023-04-20 10:24:53 +00:00
|
|
|
const auto path_var = vars.get_unless_empty(L"fish_function_path");
|
|
|
|
if (!path_var) return;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
const std::vector<wcstring> &path_list = path_var->as_list();
|
2006-02-08 17:37:18 +00:00
|
|
|
|
2016-05-01 03:31:02 +00:00
|
|
|
for (i = 0; i < path_list.size(); i++) {
|
2012-11-19 00:30:30 +00:00
|
|
|
const wcstring &ndir_str = path_list.at(i);
|
2022-09-25 19:52:44 +00:00
|
|
|
dir_iter_t dir(ndir_str);
|
2018-09-22 11:28:19 +00:00
|
|
|
if (!dir.valid()) continue;
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2022-09-25 19:52:44 +00:00
|
|
|
while (const auto *entry = dir.next()) {
|
|
|
|
const wchar_t *fn = entry->name.c_str();
|
2012-11-19 00:30:30 +00:00
|
|
|
const wchar_t *suffix;
|
2016-05-01 03:31:02 +00:00
|
|
|
if (!get_hidden && fn[0] == L'_') continue;
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2019-03-12 21:06:01 +00:00
|
|
|
suffix = std::wcsrchr(fn, L'.');
|
2022-10-29 08:24:03 +00:00
|
|
|
// We need a ".fish" *suffix*, it can't be the entire name.
|
|
|
|
if (suffix && suffix != fn && (std::wcscmp(suffix, L".fish") == 0)) {
|
|
|
|
// Also ignore directories.
|
|
|
|
if (!entry->is_dir()) {
|
|
|
|
wcstring name(fn, suffix - fn);
|
|
|
|
names.insert(name);
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2006-02-08 09:20:05 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 20:56:32 +00:00
|
|
|
void function_add(wcstring name, std::shared_ptr<function_properties_t> props) {
|
2021-10-21 20:34:46 +00:00
|
|
|
assert(props && "Null props");
|
2019-04-26 22:03:41 +00:00
|
|
|
auto funcset = function_set.acquire();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
// Historical check. TODO: rationalize this.
|
2019-11-12 19:25:41 +00:00
|
|
|
if (name.empty()) {
|
2019-04-26 22:03:41 +00:00
|
|
|
return;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 03:31:02 +00:00
|
|
|
// Remove the old function.
|
2019-11-12 19:25:41 +00:00
|
|
|
funcset->remove(name);
|
2019-04-26 22:03:41 +00:00
|
|
|
|
|
|
|
// Check if this is a function that we are autoloading.
|
2021-10-21 20:23:49 +00:00
|
|
|
props->is_autoload = funcset->autoloader.autoload_in_progress(name);
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 03:31:02 +00:00
|
|
|
// Create and store a new function.
|
2021-10-21 21:17:16 +00:00
|
|
|
auto ins = funcset->funcs.emplace(std::move(name), std::move(props));
|
2019-04-26 22:03:41 +00:00
|
|
|
assert(ins.second && "Function should not already be present in the table");
|
|
|
|
(void)ins;
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 21:32:35 +00:00
|
|
|
function_properties_ref_t function_get_props(const wcstring &name) {
|
2018-02-12 01:21:24 +00:00
|
|
|
if (parser_keywords_is_reserved(name)) return nullptr;
|
2021-10-21 21:17:16 +00:00
|
|
|
return function_set.acquire()->get_props(name);
|
2018-02-12 01:21:24 +00:00
|
|
|
}
|
|
|
|
|
2023-04-10 16:27:35 +00:00
|
|
|
wcstring function_get_definition_file(const function_properties_t &props) {
|
|
|
|
return props.definition_file ? *props.definition_file : L"";
|
|
|
|
}
|
|
|
|
|
|
|
|
wcstring function_get_copy_definition_file(const function_properties_t &props) {
|
|
|
|
return props.copy_definition_file ? *props.copy_definition_file : L"";
|
|
|
|
}
|
2023-04-19 20:39:38 +00:00
|
|
|
bool function_is_copy(const function_properties_t &props) { return props.is_copy; }
|
2023-04-10 16:27:35 +00:00
|
|
|
int function_get_definition_lineno(const function_properties_t &props) {
|
|
|
|
return props.definition_lineno();
|
|
|
|
}
|
|
|
|
int function_get_copy_definition_lineno(const function_properties_t &props) {
|
|
|
|
return props.copy_definition_lineno;
|
|
|
|
}
|
|
|
|
|
2023-04-19 20:39:38 +00:00
|
|
|
wcstring function_get_annotated_definition(const function_properties_t &props,
|
|
|
|
const wcstring &name) {
|
2023-04-10 16:27:35 +00:00
|
|
|
return props.annotated_definition(name);
|
|
|
|
}
|
|
|
|
|
2021-10-21 21:28:39 +00:00
|
|
|
function_properties_ref_t function_get_props_autoload(const wcstring &name, parser_t &parser) {
|
2022-06-19 23:27:06 +00:00
|
|
|
parser.assert_can_execute();
|
2021-10-21 21:28:39 +00:00
|
|
|
if (parser_keywords_is_reserved(name)) return nullptr;
|
2022-06-11 18:34:52 +00:00
|
|
|
function_load(name, parser);
|
2021-10-21 21:28:39 +00:00
|
|
|
return function_get_props(name);
|
2011-12-27 03:18:46 +00:00
|
|
|
}
|
2010-09-18 01:51:16 +00:00
|
|
|
|
2021-10-21 21:28:39 +00:00
|
|
|
bool function_exists(const wcstring &cmd, parser_t &parser) {
|
2022-06-19 23:27:06 +00:00
|
|
|
parser.assert_can_execute();
|
2021-10-21 21:28:39 +00:00
|
|
|
if (!valid_func_name(cmd)) return false;
|
|
|
|
return function_get_props_autoload(cmd, parser) != nullptr;
|
2015-10-08 01:59:41 +00:00
|
|
|
}
|
|
|
|
|
2021-06-22 19:37:45 +00:00
|
|
|
bool function_exists_no_autoload(const wcstring &cmd) {
|
|
|
|
if (!valid_func_name(cmd)) return false;
|
|
|
|
if (parser_keywords_is_reserved(cmd)) return false;
|
2019-04-26 22:03:41 +00:00
|
|
|
auto funcset = function_set.acquire();
|
2015-03-11 13:14:56 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
// Check if we either have the function, or it could be autoloaded.
|
2021-10-21 21:17:16 +00:00
|
|
|
return funcset->get_props(cmd) || funcset->autoloader.can_autoload(cmd);
|
2012-01-26 02:40:08 +00:00
|
|
|
}
|
2006-02-05 13:10:35 +00:00
|
|
|
|
2019-04-26 22:03:41 +00: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;
|
2012-01-24 04:32:36 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
void function_remove(const wcstring &name) {
|
|
|
|
auto funcset = function_set.acquire();
|
2019-06-22 09:08:36 +00:00
|
|
|
funcset->remove(name);
|
|
|
|
// Prevent (re-)autoloading this function.
|
|
|
|
funcset->autoload_tombstones.insert(name);
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2021-10-21 21:28:39 +00:00
|
|
|
// \return the body of a function (everything after the header, up to but not including the 'end').
|
|
|
|
static wcstring get_function_body_source(const function_properties_t &props) {
|
2020-01-03 22:40:28 +00:00
|
|
|
// We want to preserve comments that the AST attaches to the header (#5285).
|
2020-07-03 18:16:51 +00:00
|
|
|
// Take everything from the end of the header to the 'end' keyword.
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
if (props.func_node->header().ptr()->try_source_range() &&
|
|
|
|
props.func_node->end().try_source_range()) {
|
|
|
|
auto header_src = props.func_node->header().ptr()->source_range();
|
|
|
|
auto end_kw_src = props.func_node->end().range();
|
|
|
|
uint32_t body_start = header_src.start + header_src.length;
|
|
|
|
uint32_t body_end = end_kw_src.start;
|
2020-07-03 18:16:51 +00:00
|
|
|
assert(body_start <= body_end && "end keyword should come after header");
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
return wcstring(props.parsed_source->src(), body_start, body_end - body_start);
|
2012-05-18 02:37:46 +00:00
|
|
|
}
|
2021-10-21 21:28:39 +00:00
|
|
|
return wcstring{};
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-05-05 03:20:52 +00:00
|
|
|
void function_set_desc(const wcstring &name, const wcstring &desc, parser_t &parser) {
|
2022-06-19 23:27:06 +00:00
|
|
|
parser.assert_can_execute();
|
2022-06-11 18:34:52 +00:00
|
|
|
function_load(name, parser);
|
2019-04-26 22:03:41 +00:00
|
|
|
auto funcset = function_set.acquire();
|
|
|
|
auto iter = funcset->funcs.find(name);
|
|
|
|
if (iter != funcset->funcs.end()) {
|
2021-10-21 20:56:32 +00:00
|
|
|
// Note the description is immutable, as it may be accessed on another thread, so we copy
|
|
|
|
// the properties to modify it.
|
2021-10-21 21:17:16 +00:00
|
|
|
auto new_props = copy_props(iter->second);
|
2021-10-21 20:56:32 +00:00
|
|
|
new_props->description = desc;
|
2021-10-21 21:17:16 +00:00
|
|
|
iter->second = new_props;
|
2012-05-18 21:00:36 +00:00
|
|
|
}
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2023-02-13 15:59:28 +00:00
|
|
|
bool function_copy(const wcstring &name, const wcstring &new_name, parser_t &parser) {
|
|
|
|
auto filename = parser.current_filename();
|
|
|
|
auto lineno = parser.get_lineno();
|
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
auto funcset = function_set.acquire();
|
2021-10-21 21:17:16 +00:00
|
|
|
auto props = funcset->get_props(name);
|
|
|
|
if (!props) {
|
2019-04-26 22:03:41 +00:00
|
|
|
// No such function.
|
|
|
|
return false;
|
2012-01-14 07:44:18 +00:00
|
|
|
}
|
2021-10-21 20:23:49 +00:00
|
|
|
// Copy the function's props.
|
2021-10-21 21:17:16 +00:00
|
|
|
auto new_props = copy_props(props);
|
2021-10-21 20:23:49 +00:00
|
|
|
new_props->is_autoload = false;
|
2023-02-13 15:59:28 +00:00
|
|
|
new_props->is_copy = true;
|
|
|
|
new_props->copy_definition_file = filename;
|
|
|
|
new_props->copy_definition_lineno = lineno;
|
2021-10-21 20:23:49 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
// Note this will NOT overwrite an existing function with the new name.
|
|
|
|
// TODO: rationalize if this behavior is desired.
|
2021-10-21 21:17:16 +00:00
|
|
|
funcset->funcs.emplace(new_name, std::move(new_props));
|
2019-04-26 22:03:41 +00:00
|
|
|
return true;
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> function_get_names(bool get_hidden) {
|
2017-08-19 20:29:52 +00:00
|
|
|
std::unordered_set<wcstring> names;
|
2019-04-26 22:03:41 +00:00
|
|
|
auto funcset = function_set.acquire();
|
2012-11-19 00:30:30 +00:00
|
|
|
autoload_names(names, get_hidden);
|
2019-04-26 22:03:41 +00:00
|
|
|
for (const auto &func : funcset->funcs) {
|
2017-08-19 20:29:52 +00:00
|
|
|
const wcstring &name = func.first;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 03:31:02 +00:00
|
|
|
// Maybe skip hidden.
|
2016-10-22 18:21:13 +00:00
|
|
|
if (!get_hidden && (name.empty() || name.at(0) == L'_')) {
|
|
|
|
continue;
|
2012-01-14 07:44:18 +00:00
|
|
|
}
|
|
|
|
names.insert(name);
|
|
|
|
}
|
2023-04-18 22:19:10 +00:00
|
|
|
return std::vector<wcstring>(names.begin(), names.end());
|
2005-09-20 13:26:39 +00:00
|
|
|
}
|
2005-10-05 22:37:08 +00:00
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
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();
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> autoloadees;
|
2019-04-26 22:03:41 +00:00
|
|
|
for (const auto &kv : funcset->funcs) {
|
2021-10-21 21:17:16 +00:00
|
|
|
if (kv.second->is_autoload) {
|
2019-04-26 22:03:41 +00:00
|
|
|
autoloadees.push_back(kv.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const wcstring &name : autoloadees) {
|
|
|
|
funcset->remove(name);
|
|
|
|
}
|
|
|
|
funcset->autoloader.clear();
|
|
|
|
}
|
2020-09-21 15:44:58 +00:00
|
|
|
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
function_properties_t::function_properties_t() : parsed_source(empty_parsed_source_ref()) {}
|
|
|
|
|
|
|
|
function_properties_t::function_properties_t(const function_properties_t &other)
|
|
|
|
: parsed_source(empty_parsed_source_ref()) {
|
|
|
|
*this = other;
|
|
|
|
}
|
|
|
|
|
|
|
|
function_properties_t &function_properties_t::operator=(const function_properties_t &other) {
|
|
|
|
parsed_source = other.parsed_source->clone();
|
|
|
|
func_node = other.func_node;
|
|
|
|
named_arguments = other.named_arguments;
|
|
|
|
description = other.description;
|
|
|
|
inherit_vars = other.inherit_vars;
|
|
|
|
shadow_scope = other.shadow_scope;
|
|
|
|
is_autoload = other.is_autoload;
|
|
|
|
definition_file = other.definition_file;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2021-10-21 21:28:39 +00:00
|
|
|
wcstring function_properties_t::annotated_definition(const wcstring &name) const {
|
2020-09-21 15:44:58 +00:00
|
|
|
wcstring out;
|
2021-10-21 21:28:39 +00:00
|
|
|
wcstring desc = this->localized_description();
|
|
|
|
wcstring def = get_function_body_source(*this);
|
2023-02-11 20:31:08 +00:00
|
|
|
auto handlers = event_get_function_handler_descs(name);
|
2020-09-21 15:44:58 +00:00
|
|
|
|
|
|
|
out.append(L"function ");
|
|
|
|
|
|
|
|
// Typically we prefer to specify the function name first, e.g. "function foo --description bar"
|
|
|
|
// But if the function name starts with a -, we'll need to output it after all the options.
|
|
|
|
bool defer_function_name = (name.at(0) == L'-');
|
|
|
|
if (!defer_function_name) {
|
2022-07-25 14:25:04 +00:00
|
|
|
out.append(escape_string(name));
|
2020-09-21 15:44:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Output wrap targets.
|
|
|
|
for (const wcstring &wrap : complete_get_wrap_targets(name)) {
|
|
|
|
out.append(L" --wraps=");
|
2022-07-25 14:25:04 +00:00
|
|
|
out.append(escape_string(wrap));
|
2020-09-21 15:44:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!desc.empty()) {
|
|
|
|
out.append(L" --description ");
|
2022-07-25 14:25:04 +00:00
|
|
|
out.append(escape_string(desc));
|
2020-09-21 15:44:58 +00:00
|
|
|
}
|
|
|
|
|
2021-10-21 21:28:39 +00:00
|
|
|
if (!this->shadow_scope) {
|
2020-09-21 15:44:58 +00:00
|
|
|
out.append(L" --no-scope-shadowing");
|
|
|
|
}
|
|
|
|
|
2023-02-11 20:31:08 +00:00
|
|
|
for (const auto &d : handlers) {
|
|
|
|
switch (d.typ) {
|
2020-09-21 15:44:58 +00:00
|
|
|
case event_type_t::signal: {
|
2023-04-30 22:40:06 +00:00
|
|
|
append_format(out, L" --on-signal %ls", sig2wcs(d.signal)->c_str());
|
2020-09-21 15:44:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case event_type_t::variable: {
|
2023-02-11 20:31:08 +00:00
|
|
|
append_format(out, L" --on-variable %ls", d.str_param1->c_str());
|
2020-09-21 15:44:58 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-05-19 18:29:03 +00:00
|
|
|
case event_type_t::process_exit: {
|
2023-02-11 20:31:08 +00:00
|
|
|
append_format(out, L" --on-process-exit %d", d.pid);
|
2021-05-19 18:29:03 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case event_type_t::job_exit: {
|
2023-02-11 20:31:08 +00:00
|
|
|
append_format(out, L" --on-job-exit %d", d.pid);
|
2020-09-21 15:44:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case event_type_t::caller_exit: {
|
|
|
|
append_format(out, L" --on-job-exit caller");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case event_type_t::generic: {
|
2023-02-11 20:31:08 +00:00
|
|
|
append_format(out, L" --on-event %ls", d.str_param1->c_str());
|
2020-09-21 15:44:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case event_type_t::any:
|
|
|
|
default: {
|
2023-02-11 20:31:08 +00:00
|
|
|
DIE("unexpected next->typ");
|
2020-09-21 15:44:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
const std::vector<wcstring> &named = this->named_arguments;
|
2020-09-21 15:44:58 +00:00
|
|
|
if (!named.empty()) {
|
|
|
|
append_format(out, L" --argument");
|
|
|
|
for (const auto &name : named) {
|
|
|
|
append_format(out, L" %ls", name.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Output the function name if we deferred it.
|
|
|
|
if (defer_function_name) {
|
|
|
|
out.append(L" -- ");
|
2022-07-25 14:25:04 +00:00
|
|
|
out.append(escape_string(name));
|
2020-09-21 15:44:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Output any inherited variables as `set -l` lines.
|
2021-10-21 21:28:39 +00:00
|
|
|
for (const auto &kv : this->inherit_vars) {
|
2020-09-21 15:44:58 +00:00
|
|
|
// We don't know what indentation style the function uses,
|
|
|
|
// so we do what fish_indent would.
|
|
|
|
append_format(out, L"\n set -l %ls", kv.first.c_str());
|
|
|
|
for (const auto &arg : kv.second) {
|
|
|
|
out.push_back(L' ');
|
2022-07-25 14:25:04 +00:00
|
|
|
out.append(escape_string(arg));
|
2020-09-21 15:44:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
out.push_back('\n');
|
|
|
|
out.append(def);
|
|
|
|
|
|
|
|
// Append a newline before the 'end', unless there already is one there.
|
|
|
|
if (!string_suffixes_string(L"\n", def)) {
|
|
|
|
out.push_back(L'\n');
|
|
|
|
}
|
|
|
|
out.append(L"end\n");
|
|
|
|
return out;
|
|
|
|
}
|
2021-10-21 21:28:39 +00:00
|
|
|
|
|
|
|
const wchar_t *function_properties_t::localized_description() const {
|
|
|
|
if (description.empty()) return L"";
|
|
|
|
return _(description.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
int function_properties_t::definition_lineno() const {
|
|
|
|
// 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?
|
Port AST to Rust
The translation is fairly direct though it adds some duplication, for example
there are multiple "match" statements that mimic function overloading.
Rust has no overloading, and we cannot have generic methods in the Node trait
(due to a Rust limitation, the error is like "cannot be made into an object")
so we include the type name in method names.
Give clients like "indent_visitor_t" a Rust companion ("IndentVisitor")
that takes care of the AST traversal while the AST consumption remains
in C++ for now. In future, "IndentVisitor" should absorb the entirety of
"indent_visitor_t". This pattern requires that "fish_indent" be exposed
includable header to the CXX bridge.
Alternatively, we could define FFI wrappers for recursive AST traversal.
Rust requires we separate the AST visitors for "mut" and "const"
scenarios. Take this opportunity to concretize both visitors:
The only client that requires mutable access is the populator. To match the
structure of the C++ populator which makes heavy use of function overloading,
we need to add a bunch of functions to the trait. Since there is no other
mutable visit, this seems acceptable.
The "const" visitors never use "will_visit_fields_of()" or
"did_visit_fields_of()", so remove them (though this is debatable).
Like in the C++ implementation, the AST nodes themselves are largely defined
via macros. Union fields like "Statement" and "ArgumentOrRedirection"
do currently not use macros but may in future.
This commit also introduces a precedent for a type that is defined in one
CXX bridge and used in another one - "ParseErrorList". To make this work
we need to manually define "ExternType".
There is one annoyance with CXX: functions that take explicit lifetime
parameters require to be marked as unsafe. This makes little sense
because functions that return `&Foo` with implicit lifetime can be
misused the same way on the C++ side.
One notable change is that we cannot directly port "find_block_open_keyword()"
(which is used to compute an error) because it relies on the stack of visited
nodes. We cannot modify a stack of node references while we do the "mut"
walk. Happily, an idiomatic solution is easy: we can tell the AST visitor
to backtrack to the parent node and create the error there.
Since "node_t::accept_base" is no longer a template we don't need the
"node_visitation_t" trampoline anymore.
The added copying at the FFI boundary makes things slower (memcpy dominates
the profile) but it's not unusable, which is good news:
$ hyperfine ./fish.{old,new}" -c 'source ../share/completions/git.fish'"
Benchmark 1: ./fish.old -c 'source ../share/completions/git.fish'
Time (mean ± σ): 195.5 ms ± 2.9 ms [User: 190.1 ms, System: 4.4 ms]
Range (min … max): 193.2 ms … 205.1 ms 15 runs
Benchmark 2: ./fish.new -c 'source ../share/completions/git.fish'
Time (mean ± σ): 677.5 ms ± 62.0 ms [User: 665.4 ms, System: 10.0 ms]
Range (min … max): 611.7 ms … 805.5 ms 10 runs
Summary
'./fish.old -c 'source ../share/completions/git.fish'' ran
3.47 ± 0.32 times faster than './fish.new -c 'source ../share/completions/git.fish''
Leftovers:
- Enum variants are still snakecase; I didn't get around to changing this yet.
- "ast_type_to_string()" still returns a snakecase name. This could be
changed since it's not user visible.
2023-04-02 14:42:59 +00:00
|
|
|
assert(func_node->try_source_range() && "Function has no source range");
|
|
|
|
auto source_range = func_node->source_range();
|
|
|
|
uint32_t func_start = source_range.start;
|
|
|
|
const wcstring &source = parsed_source->src();
|
2021-10-21 21:28:39 +00:00
|
|
|
assert(func_start <= source.size() && "function start out of bounds");
|
|
|
|
return 1 + std::count(source.begin(), source.begin() + func_start, L'\n');
|
|
|
|
}
|