fish-shell/src/function.cpp

444 lines
15 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
#include "function.h"
#include <algorithm>
#include <cstdint>
#include <cwchar>
2012-01-14 07:44:18 +00:00
#include <map>
#include <memory>
2015-07-25 15:14:25 +00:00
#include <string>
#include <unordered_map>
#include <unordered_set>
2015-07-25 15:14:25 +00:00
#include <utility>
#include <vector>
#include "ast.h"
2015-07-25 15:14:25 +00:00
#include "autoload.h"
#include "common.h"
#include "complete.h"
#include "env.h"
#include "event.h"
#include "fallback.h" // IWYU pragma: keep
#include "maybe.h"
#include "parse_constants.h"
2018-09-25 02:26:46 +00:00
#include "parser.h"
#include "parser_keywords.h"
2023-01-14 22:56:24 +00:00
#include "signals.h"
#include "wcstringutil.h"
#include "wutil.h" // IWYU pragma: keep
namespace {
/// 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_properties_ref_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 properties for a function, or nullptr if none.
function_properties_ref_t get_props(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.
auto props = get_props(name);
bool has_explicit_func = props && !props->is_autoload;
bool is_tombstoned = autoload_tombstones.count(name) > 0;
return !has_explicit_func && !is_tombstoned;
}
} // namespace
/// \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) {
assert(props && "Null props");
return std::make_shared<function_properties_t>(*props);
}
/// Make sure that if the specified function is a dynamically loaded function, it has been fully
/// loaded.
/// Note this executes fish script code.
bool function_load(const wcstring &name, parser_t &parser) {
parser.assert_can_execute();
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, env_stack_t::globals());
}
}
// Release the lock and perform any autoload, then reacquire the lock and clean up.
if (path_to_autoload) {
// Crucially, the lock is acquired after perform_autoload().
autoload_t::perform_autoload(*path_to_autoload, parser);
function_set.acquire()->autoloader.mark_autoload_finished(name);
}
return path_to_autoload.has_value();
}
/// 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) {
size_t i;
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();
const auto path_var = vars.get_unless_empty(L"fish_function_path");
if (!path_var) return;
const std::vector<wcstring> &path_list = path_var->as_list();
for (i = 0; i < path_list.size(); i++) {
const wcstring &ndir_str = path_list.at(i);
dir_iter_t dir(ndir_str);
if (!dir.valid()) continue;
while (const auto *entry = dir.next()) {
const wchar_t *fn = entry->name.c_str();
const wchar_t *suffix;
if (!get_hidden && fn[0] == L'_') continue;
suffix = std::wcsrchr(fn, L'.');
// 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);
}
}
}
}
}
void function_add(wcstring name, std::shared_ptr<function_properties_t> props) {
assert(props && "Null props");
auto funcset = function_set.acquire();
// Historical check. TODO: rationalize this.
if (name.empty()) {
return;
}
// Remove the old function.
funcset->remove(name);
// Check if this is a function that we are autoloading.
props->is_autoload = funcset->autoloader.autoload_in_progress(name);
// Create and store a new function.
auto ins = funcset->funcs.emplace(std::move(name), std::move(props));
assert(ins.second && "Function should not already be present in the table");
(void)ins;
}
function_properties_ref_t function_get_props(const wcstring &name) {
if (parser_keywords_is_reserved(name)) return nullptr;
return function_set.acquire()->get_props(name);
}
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"";
}
bool function_is_copy(const function_properties_t &props) { return props.is_copy; }
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;
}
wcstring function_get_annotated_definition(const function_properties_t &props,
const wcstring &name) {
return props.annotated_definition(name);
}
function_properties_ref_t function_get_props_autoload(const wcstring &name, parser_t &parser) {
parser.assert_can_execute();
if (parser_keywords_is_reserved(name)) return nullptr;
function_load(name, parser);
return function_get_props(name);
}
bool function_exists(const wcstring &cmd, parser_t &parser) {
parser.assert_can_execute();
if (!valid_func_name(cmd)) return false;
return function_get_props_autoload(cmd, parser) != nullptr;
}
bool function_exists_no_autoload(const wcstring &cmd) {
if (!valid_func_name(cmd)) return false;
if (parser_keywords_is_reserved(cmd)) return false;
auto funcset = function_set.acquire();
// Check if we either have the function, or it could be autoloaded.
return funcset->get_props(cmd) || funcset->autoloader.can_autoload(cmd);
2012-01-26 02:40:08 +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;
}
void function_remove(const wcstring &name) {
auto funcset = function_set.acquire();
funcset->remove(name);
// Prevent (re-)autoloading this function.
funcset->autoload_tombstones.insert(name);
}
// \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) {
// We want to preserve comments that the AST attaches to the header (#5285).
// 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;
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);
}
return wcstring{};
}
void function_set_desc(const wcstring &name, const wcstring &desc, parser_t &parser) {
parser.assert_can_execute();
function_load(name, parser);
auto funcset = function_set.acquire();
auto iter = funcset->funcs.find(name);
if (iter != funcset->funcs.end()) {
// Note the description is immutable, as it may be accessed on another thread, so we copy
// the properties to modify it.
auto new_props = copy_props(iter->second);
new_props->description = desc;
iter->second = new_props;
}
}
bool function_copy(const wcstring &name, const wcstring &new_name, parser_t &parser) {
auto filename = parser.current_filename();
auto lineno = parser.get_lineno();
auto funcset = function_set.acquire();
auto props = funcset->get_props(name);
if (!props) {
// No such function.
return false;
2012-01-14 07:44:18 +00:00
}
// Copy the function's props.
auto new_props = copy_props(props);
new_props->is_autoload = false;
new_props->is_copy = true;
new_props->copy_definition_file = filename;
new_props->copy_definition_lineno = lineno;
// Note this will NOT overwrite an existing function with the new name.
// TODO: rationalize if this behavior is desired.
funcset->funcs.emplace(new_name, std::move(new_props));
return true;
}
std::vector<wcstring> function_get_names(bool 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-19 20:29:52 +00: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-19 20:29:52 +00:00
const wcstring &name = func.first;
// Maybe skip hidden.
if (!get_hidden && (name.empty() || name.at(0) == L'_')) {
continue;
2012-01-14 07:44:18 +00:00
}
names.insert(name);
}
return std::vector<wcstring>(names.begin(), names.end());
}
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();
std::vector<wcstring> 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();
}
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;
}
wcstring function_properties_t::annotated_definition(const wcstring &name) const {
wcstring out;
wcstring desc = this->localized_description();
wcstring def = get_function_body_source(*this);
auto handlers = event_get_function_handler_descs(name);
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) {
out.append(escape_string(name));
}
// Output wrap targets.
for (const wcstring &wrap : complete_get_wrap_targets(name)) {
out.append(L" --wraps=");
out.append(escape_string(wrap));
}
if (!desc.empty()) {
out.append(L" --description ");
out.append(escape_string(desc));
}
if (!this->shadow_scope) {
out.append(L" --no-scope-shadowing");
}
for (const auto &d : handlers) {
switch (d.typ) {
case event_type_t::signal: {
append_format(out, L" --on-signal %ls", sig2wcs(d.signal)->c_str());
break;
}
case event_type_t::variable: {
append_format(out, L" --on-variable %ls", d.str_param1->c_str());
break;
}
case event_type_t::process_exit: {
append_format(out, L" --on-process-exit %d", d.pid);
break;
}
case event_type_t::job_exit: {
append_format(out, L" --on-job-exit %d", d.pid);
break;
}
case event_type_t::caller_exit: {
append_format(out, L" --on-job-exit caller");
break;
}
case event_type_t::generic: {
append_format(out, L" --on-event %ls", d.str_param1->c_str());
break;
}
case event_type_t::any:
default: {
DIE("unexpected next->typ");
}
}
}
const std::vector<wcstring> &named = this->named_arguments;
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" -- ");
out.append(escape_string(name));
}
// Output any inherited variables as `set -l` lines.
for (const auto &kv : this->inherit_vars) {
// 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' ');
out.append(escape_string(arg));
}
}
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;
}
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();
assert(func_start <= source.size() && "function start out of bounds");
return 1 + std::count(source.begin(), source.begin() + func_start, L'\n');
}