mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-12-28 05:14:13 +08:00
971d257e67
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.
140 lines
5.0 KiB
Rust
140 lines
5.0 KiB
Rust
use crate::ast::{
|
|
Argument, Ast, BlockStatement, BlockStatementHeaderVariant, DecoratedStatement, Keyword, Node,
|
|
NodeFfi, NodeVisitor, Redirection, Token, Type, VariableAssignment,
|
|
};
|
|
use crate::ffi::highlighter_t;
|
|
use crate::parse_constants::ParseTokenType;
|
|
use std::pin::Pin;
|
|
|
|
struct Highlighter<'a> {
|
|
companion: Pin<&'a mut highlighter_t>,
|
|
ast: &'a Ast,
|
|
}
|
|
impl<'a> Highlighter<'a> {
|
|
// Visit the children of a node.
|
|
fn visit_children(&mut self, node: &'a dyn Node) {
|
|
node.accept(self, false);
|
|
}
|
|
// AST visitor implementations.
|
|
fn visit_keyword(&mut self, node: &dyn Keyword) {
|
|
let ffi_node = NodeFfi::new(node.leaf_as_node_ffi());
|
|
self.companion
|
|
.as_mut()
|
|
.visit_keyword((&ffi_node as *const NodeFfi<'_>).cast());
|
|
}
|
|
fn visit_token(&mut self, node: &dyn Token) {
|
|
let ffi_node = NodeFfi::new(node.leaf_as_node_ffi());
|
|
self.companion
|
|
.as_mut()
|
|
.visit_token((&ffi_node as *const NodeFfi<'_>).cast());
|
|
}
|
|
fn visit_argument(&mut self, node: &Argument) {
|
|
self.companion
|
|
.as_mut()
|
|
.visit_argument((node as *const Argument).cast(), false, true);
|
|
}
|
|
fn visit_redirection(&mut self, node: &Redirection) {
|
|
self.companion
|
|
.as_mut()
|
|
.visit_redirection((node as *const Redirection).cast());
|
|
}
|
|
fn visit_variable_assignment(&mut self, node: &VariableAssignment) {
|
|
self.companion
|
|
.as_mut()
|
|
.visit_variable_assignment((node as *const VariableAssignment).cast());
|
|
}
|
|
fn visit_semi_nl(&mut self, node: &dyn Node) {
|
|
let ffi_node = NodeFfi::new(node);
|
|
self.companion
|
|
.as_mut()
|
|
.visit_semi_nl((&ffi_node as *const NodeFfi<'_>).cast());
|
|
}
|
|
fn visit_decorated_statement(&mut self, node: &DecoratedStatement) {
|
|
self.companion
|
|
.as_mut()
|
|
.visit_decorated_statement((node as *const DecoratedStatement).cast());
|
|
}
|
|
fn visit_block_statement(&mut self, node: &'a BlockStatement) {
|
|
match &*node.header {
|
|
BlockStatementHeaderVariant::None => panic!(),
|
|
BlockStatementHeaderVariant::ForHeader(node) => self.visit(node),
|
|
BlockStatementHeaderVariant::WhileHeader(node) => self.visit(node),
|
|
BlockStatementHeaderVariant::FunctionHeader(node) => self.visit(node),
|
|
BlockStatementHeaderVariant::BeginHeader(node) => self.visit(node),
|
|
}
|
|
self.visit(&node.args_or_redirs);
|
|
let pending_variables_count = self
|
|
.companion
|
|
.as_mut()
|
|
.visit_block_statement1((node as *const BlockStatement).cast());
|
|
self.visit(&node.jobs);
|
|
self.visit(&node.end);
|
|
self.companion
|
|
.as_mut()
|
|
.visit_block_statement2(pending_variables_count);
|
|
}
|
|
}
|
|
|
|
impl<'a> NodeVisitor<'a> for Highlighter<'a> {
|
|
fn visit(&mut self, node: &'a dyn Node) {
|
|
if let Some(keyword) = node.as_keyword() {
|
|
return self.visit_keyword(keyword);
|
|
}
|
|
if let Some(token) = node.as_token() {
|
|
if token.token_type() == ParseTokenType::end {
|
|
self.visit_semi_nl(node);
|
|
return;
|
|
}
|
|
self.visit_token(token);
|
|
return;
|
|
}
|
|
match node.typ() {
|
|
Type::argument => self.visit_argument(node.as_argument().unwrap()),
|
|
Type::redirection => self.visit_redirection(node.as_redirection().unwrap()),
|
|
Type::variable_assignment => {
|
|
self.visit_variable_assignment(node.as_variable_assignment().unwrap())
|
|
}
|
|
Type::decorated_statement => {
|
|
self.visit_decorated_statement(node.as_decorated_statement().unwrap())
|
|
}
|
|
Type::block_statement => self.visit_block_statement(node.as_block_statement().unwrap()),
|
|
// Default implementation is to just visit children.
|
|
_ => self.visit_children(node),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cxx::bridge]
|
|
#[allow(clippy::needless_lifetimes)] // false positive
|
|
mod highlighter_ffi {
|
|
extern "C++" {
|
|
include!("ast.h");
|
|
include!("highlight.h");
|
|
include!("parse_constants.h");
|
|
type highlighter_t = crate::ffi::highlighter_t;
|
|
type Ast = crate::ast::Ast;
|
|
type NodeFfi<'a> = crate::ast::NodeFfi<'a>;
|
|
}
|
|
extern "Rust" {
|
|
type Highlighter<'a>;
|
|
unsafe fn new_highlighter<'a>(
|
|
companion: Pin<&'a mut highlighter_t>,
|
|
ast: &'a Ast,
|
|
) -> Box<Highlighter<'a>>;
|
|
#[cxx_name = "visit_children"]
|
|
unsafe fn visit_children_ffi<'a>(self: &mut Highlighter<'a>, node: &'a NodeFfi<'a>);
|
|
}
|
|
}
|
|
|
|
fn new_highlighter<'a>(
|
|
companion: Pin<&'a mut highlighter_t>,
|
|
ast: &'a Ast,
|
|
) -> Box<Highlighter<'a>> {
|
|
Box::new(Highlighter { companion, ast })
|
|
}
|
|
impl<'a> Highlighter<'a> {
|
|
fn visit_children_ffi(&mut self, node: &'a NodeFfi<'a>) {
|
|
self.visit_children(node.as_node());
|
|
}
|
|
}
|