ast: clean up lists

Make working with lists more natural
This commit is contained in:
Peter Ammon
2025-04-26 12:06:25 -07:00
parent 325232bec1
commit e05ecd6c7d
7 changed files with 70 additions and 149 deletions

View File

@@ -25,7 +25,8 @@
};
use crate::wchar::prelude::*;
use std::borrow::Cow;
use std::ops::{ControlFlow, Index, IndexMut};
use std::convert::AsMut;
use std::ops::{ControlFlow, Deref};
/**
* A NodeVisitor is something which can visit an AST node.
@@ -486,28 +487,6 @@ fn allows_keyword(&self, kw: ParseKeyword) -> bool {
}
}
// A simple variable-sized array, possibly empty.
pub trait List: Node {
type ContentsNode: Node + Default;
fn contents(&self) -> &[Self::ContentsNode];
fn contents_mut(&mut self) -> &mut Box<[Self::ContentsNode]>;
/// Return our count.
fn count(&self) -> usize {
self.contents().len()
}
/// Return whether we are empty.
fn is_empty(&self) -> bool {
self.contents().is_empty()
}
/// Iteration support.
fn iter(&self) -> std::slice::Iter<Self::ContentsNode> {
self.contents().iter()
}
fn get(&self, index: usize) -> Option<&Self::ContentsNode> {
self.contents().get(index)
}
}
/// This is for optional values and for lists.
trait CheckParse {
/// A true return means we should descend into the production, false means stop.
@@ -642,7 +621,7 @@ impl $name {
}
}
/// Define a node that implements the list trait.
/// Define a list node.
macro_rules! define_list_node {
(
$name:ident,
@@ -650,97 +629,51 @@ macro_rules! define_list_node {
$contents:ident
) => {
#[derive(Default, Debug)]
pub struct $name {
list_contents: Box<[$contents]>,
}
pub struct $name(Box<[$contents]>);
implement_node!($name, $type);
impl List for $name {
type ContentsNode = $contents;
fn contents(&self) -> &[Self::ContentsNode] {
&self.list_contents
}
fn contents_mut(&mut self) -> &mut Box<[Self::ContentsNode]> {
&mut self.list_contents
impl Deref for $name {
type Target = Box<[$contents]>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> IntoIterator for &'a $name {
type Item = &'a $contents;
type IntoIter = std::slice::Iter<'a, $contents>;
fn into_iter(self) -> Self::IntoIter {
self.contents().into_iter()
self.0.iter()
}
}
impl Index<usize> for $name {
type Output = <$name as List>::ContentsNode;
fn index(&self, index: usize) -> &Self::Output {
&self.contents()[index]
}
}
impl IndexMut<usize> for $name {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.contents_mut()[index]
impl AsMut<Box<[$contents]>> for $name {
fn as_mut(&mut self) -> &mut Box<[$contents]> {
&mut self.0
}
}
impl Acceptor for $name {
#[allow(unused_variables)]
fn accept<'a>(&'a self, visitor: &mut dyn NodeVisitor<'a>) {
let _ = accept_list_visitor!(Self, accept, visit, self, visitor, $contents);
self.iter().for_each(|item| visitor.visit(item));
}
}
impl AcceptorMut for $name {
#[allow(unused_variables)]
fn accept_mut(&mut self, visitor: &mut dyn NodeVisitorMut) {
visitor.will_visit_fields_of(self);
let flow =
accept_list_visitor!(Self, accept_mut, visit_mut, self, visitor, $contents);
let flow = self
.0
.iter_mut()
.try_for_each(|item| visitor.visit_mut(item));
visitor.did_visit_fields_of(self, flow);
}
}
};
}
macro_rules! accept_list_visitor {
(
$Self:ident,
$accept:ident,
$visit:ident,
$self:ident,
$visitor:ident,
$list_element:ident
) => {
loop {
let mut result = VisitResult::Continue(());
// list types pretend their child nodes are direct embeddings.
// This isn't used during AST construction because we need to construct the list.
for i in 0..$self.count() {
result = accept_list_visitor_impl!($self, $visitor, $visit, $self[i]);
if result.is_break() {
break;
}
}
break result;
}
};
}
macro_rules! accept_list_visitor_impl {
(
$self:ident,
$visitor:ident,
visit,
$child:expr) => {{
$visitor.visit(&$child);
VisitResult::Continue(())
}};
(
$self:ident,
$visitor:ident,
visit_mut,
$child:expr) => {
$visitor.visit_mut(&mut $child)
};
}
/// Implement the acceptor trait for the given branch node.
macro_rules! implement_acceptor_for_branch {
(
@@ -2734,41 +2667,31 @@ fn visit_mut(&mut self, node: &mut dyn NodeMut) -> VisitResult {
// This field is an embedding of an array of (pointers to) ContentsNode.
// Parse as many as we can.
match node.typ() {
Type::andor_job_list => self.populate_list::<AndorJobList>(
node.as_mut_andor_job_list().unwrap(),
false,
),
Type::argument_list => self
.populate_list::<ArgumentList>(node.as_mut_argument_list().unwrap(), false),
Type::argument_or_redirection_list => self
.populate_list::<ArgumentOrRedirectionList>(
node.as_mut_argument_or_redirection_list().unwrap(),
false,
),
Type::case_item_list => self.populate_list::<CaseItemList>(
node.as_mut_case_item_list().unwrap(),
false,
),
Type::elseif_clause_list => self.populate_list::<ElseifClauseList>(
node.as_mut_elseif_clause_list().unwrap(),
false,
),
Type::job_conjunction_continuation_list => self
.populate_list::<JobConjunctionContinuationList>(
node.as_mut_job_conjunction_continuation_list().unwrap(),
false,
),
Type::job_continuation_list => self.populate_list::<JobContinuationList>(
node.as_mut_job_continuation_list().unwrap(),
false,
),
Type::job_list => {
self.populate_list::<JobList>(node.as_mut_job_list().unwrap(), false)
Type::andor_job_list => {
self.populate_list(node.as_mut_andor_job_list().unwrap(), false)
}
Type::variable_assignment_list => self.populate_list::<VariableAssignmentList>(
node.as_mut_variable_assignment_list().unwrap(),
Type::argument_list => {
self.populate_list(node.as_mut_argument_list().unwrap(), false)
}
Type::argument_or_redirection_list => self
.populate_list(node.as_mut_argument_or_redirection_list().unwrap(), false),
Type::case_item_list => {
self.populate_list(node.as_mut_case_item_list().unwrap(), false)
}
Type::elseif_clause_list => {
self.populate_list(node.as_mut_elseif_clause_list().unwrap(), false)
}
Type::job_conjunction_continuation_list => self.populate_list(
node.as_mut_job_conjunction_continuation_list().unwrap(),
false,
),
Type::job_continuation_list => {
self.populate_list(node.as_mut_job_continuation_list().unwrap(), false)
}
Type::job_list => self.populate_list(node.as_mut_job_list().unwrap(), false),
Type::variable_assignment_list => {
self.populate_list(node.as_mut_variable_assignment_list().unwrap(), false)
}
_ => (),
}
}
@@ -3309,11 +3232,14 @@ fn consume_excess_token_generating_error(&mut self) {
/// Given that we are a list of type ListNodeType, whose contents type is ContentsNode,
/// populate as many elements as we can.
/// If exhaust_stream is set, then keep going until we get parse_token_type_t::terminate.
fn populate_list<ListType: List>(&mut self, list: &mut ListType, exhaust_stream: bool)
fn populate_list<ContentsType, ListType>(&mut self, list: &mut ListType, exhaust_stream: bool)
where
<ListType as List>::ContentsNode: NodeMut + CheckParse,
ContentsType: NodeMut + CheckParse + Default,
ListType: Node + AsMut<Box<[ContentsType]>>,
{
assert!(list.contents().is_empty(), "List is not initially empty");
let typ = list.typ();
let list = list.as_mut();
assert!(list.is_empty(), "List is not initially empty");
// Do not attempt to parse a list if we are unwinding.
if self.unwinding {
@@ -3327,9 +3253,9 @@ fn populate_list<ListType: List>(&mut self, list: &mut ListType, exhaust_stream:
"%*sunwinding %ls",
self.spaces(),
"",
ast_type_to_string(list.typ())
ast_type_to_string(typ)
);
assert!(list.contents().is_empty(), "Should be an empty list");
assert!(list.is_empty(), "Should be an empty list");
return;
}
@@ -3340,7 +3266,7 @@ fn populate_list<ListType: List>(&mut self, list: &mut ListType, exhaust_stream:
// If we are unwinding, then either we recover or we break the loop, dependent on the
// loop type.
if self.unwinding {
if !self.list_type_stops_unwind(list.typ()) {
if !self.list_type_stops_unwind(typ) {
break;
}
// We are going to stop unwinding.
@@ -3372,10 +3298,10 @@ fn populate_list<ListType: List>(&mut self, list: &mut ListType, exhaust_stream:
}
// Chomp semis and newlines.
self.chomp_extras(list.typ());
self.chomp_extras(typ);
// Now try parsing a node.
if let Some(node) = self.try_parse::<ListType::ContentsNode>() {
if let Some(node) = self.try_parse::<ContentsType>() {
// #7201: Minimize reallocations of contents vector
// Empirically, 99.97% of cases are 16 elements or fewer,
// with 75% being empty, so this works out best.
@@ -3399,8 +3325,8 @@ fn populate_list<ListType: List>(&mut self, list: &mut ListType, exhaust_stream:
contents.len() <= u32::MAX.try_into().unwrap(),
"Contents size out of bounds"
);
assert!(list.contents().is_empty(), "List should still be empty");
*list.contents_mut() = contents.into_boxed_slice();
assert!(list.is_empty(), "List should still be empty");
*list = contents.into_boxed_slice();
}
FLOGF!(
@@ -3408,8 +3334,8 @@ fn populate_list<ListType: List>(&mut self, list: &mut ListType, exhaust_stream:
"%*s%ls size: %lu",
self.spaces(),
"",
ast_type_to_string(list.typ()),
list.count()
ast_type_to_string(typ),
list.len()
);
}

View File

@@ -15,9 +15,7 @@
use libc::LC_ALL;
use super::prelude::*;
use crate::ast::{
self, Ast, Category, Leaf, List, Node, NodeVisitor, SourceRangeList, Traversal, Type,
};
use crate::ast::{self, Ast, Category, Leaf, Node, NodeVisitor, SourceRangeList, Traversal, Type};
use crate::common::{
str2wcstring, unescape_string, wcs2string, UnescapeFlags, UnescapeStringStyle, PROGRAM_NAME,
};
@@ -279,10 +277,10 @@ fn compute_preferred_semi_locations(&self) -> Vec<usize> {
}
// If there is no and-or tail then we always use a newline.
if andors.count() > 0 {
if !andors.is_empty() {
condition.map(&mut mark_semi_from_input);
// Mark all but last of the andor list.
for andor in andors.iter().take(andors.count() - 1) {
for andor in andors.iter().take(andors.len() - 1) {
mark_semi_from_input(andor.job.semi_nl.as_ref().unwrap());
}
}

View File

@@ -2,8 +2,7 @@
use crate::abbrs::{self, with_abbrs};
use crate::ast::{
self, Argument, Ast, BlockStatement, BlockStatementHeaderVariant, BraceStatement,
DecoratedStatement, Keyword, List, Node, NodeVisitor, Redirection, Token, Type,
VariableAssignment,
DecoratedStatement, Keyword, Node, NodeVisitor, Redirection, Token, Type, VariableAssignment,
};
use crate::builtins::shared::builtin_exists;
use crate::color::Color;

View File

@@ -1,8 +1,8 @@
//! Provides the "linkage" between an ast and actual execution structures (job_t, etc.).
use crate::ast::{
self, unescape_keyword, BlockStatementHeaderVariant, Keyword, Leaf, List, Node,
StatementVariant, Token,
self, unescape_keyword, BlockStatementHeaderVariant, Keyword, Leaf, Node, StatementVariant,
Token,
};
use crate::builtins;
use crate::builtins::shared::{

View File

@@ -1,7 +1,5 @@
//! Various mostly unrelated utility functions related to parsing, loading and evaluating fish code.
use crate::ast::{
self, is_same_node, Ast, Keyword, Leaf, List, Node, NodeVisitor, Token, Traversal,
};
use crate::ast::{self, is_same_node, Ast, Keyword, Leaf, Node, NodeVisitor, Token, Traversal};
use crate::builtins::shared::builtin_exists;
use crate::common::{
escape_string, unescape_string, valid_var_name, valid_var_name_char, EscapeFlags,

View File

@@ -1,6 +1,6 @@
// The fish parser. Contains functions for parsing and evaluating code.
use crate::ast::{self, Ast, List, Node};
use crate::ast::{self, Ast, Node};
use crate::builtins::shared::STATUS_ILLEGAL_CMD;
use crate::common::{
escape_string, wcs2string, CancelChecker, EscapeFlags, EscapeStringStyle, FilenameRef,

View File

@@ -1,4 +1,4 @@
use crate::ast::{self, is_same_node, Ast, JobPipeline, List, Node, Traversal};
use crate::ast::{self, is_same_node, Ast, JobPipeline, Node, Traversal};
use crate::common::ScopeGuard;
use crate::env::EnvStack;
use crate::expand::ExpandFlags;