From c62b09d5d105fb485fd67bb5f5bbad92248eafc1 Mon Sep 17 00:00:00 2001 From: Peter Ammon Date: Sat, 3 May 2025 15:01:14 -0700 Subject: [PATCH] ast: Switch from some types to kinds Preparing to remove types entirely --- src/ast.rs | 223 +++++++++++++++++++++++++++-------------------------- 1 file changed, 112 insertions(+), 111 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 5681e4f14..cfa9166fc 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -98,7 +98,7 @@ pub trait Node: Acceptor + ConcreteNode + AsNode + std::fmt::Debug { /// Return a helpful string description of this node. fn describe(&self) -> WString { - let mut res = ast_type_to_string(self.typ()).to_owned(); + let mut res = ast_kind_to_string(self.kind()).to_owned(); if let Some(n) = self.as_token() { let token_type = n.token_type().to_wstr(); sprintf!(=> &mut res, " '%ls'", token_type); @@ -1538,48 +1538,46 @@ fn accept_mut(&mut self, visitor: &mut dyn NodeVisitorMut) { } /// Return a string literal name for an ast type. -pub fn ast_type_to_string(t: Type) -> &'static wstr { - match t { - Type::token_base => L!("token_base"), - Type::keyword_base => L!("keyword_base"), - Type::redirection => L!("redirection"), - Type::variable_assignment => L!("variable_assignment"), - Type::variable_assignment_list => L!("variable_assignment_list"), - Type::argument_or_redirection => L!("argument_or_redirection"), - Type::argument_or_redirection_list => L!("argument_or_redirection_list"), - Type::statement => L!("statement"), - Type::job_pipeline => L!("job_pipeline"), - Type::job_conjunction => L!("job_conjunction"), - Type::block_statement_header => L!("block_statement_header"), - Type::for_header => L!("for_header"), - Type::while_header => L!("while_header"), - Type::function_header => L!("function_header"), - Type::begin_header => L!("begin_header"), - Type::block_statement => L!("block_statement"), - Type::brace_statement => L!("brace_statement"), - Type::if_clause => L!("if_clause"), - Type::elseif_clause => L!("elseif_clause"), - Type::elseif_clause_list => L!("elseif_clause_list"), - Type::else_clause => L!("else_clause"), - Type::if_statement => L!("if_statement"), - Type::case_item => L!("case_item"), - Type::switch_statement => L!("switch_statement"), - Type::decorated_statement => L!("decorated_statement"), - Type::not_statement => L!("not_statement"), - Type::job_continuation => L!("job_continuation"), - Type::job_continuation_list => L!("job_continuation_list"), - Type::job_conjunction_continuation => L!("job_conjunction_continuation"), - Type::andor_job => L!("andor_job"), - Type::andor_job_list => L!("andor_job_list"), - Type::freestanding_argument_list => L!("freestanding_argument_list"), - Type::token_conjunction => L!("token_conjunction"), - Type::job_conjunction_continuation_list => L!("job_conjunction_continuation_list"), - Type::maybe_newlines => L!("maybe_newlines"), - Type::token_pipe => L!("token_pipe"), - Type::case_item_list => L!("case_item_list"), - Type::argument => L!("argument"), - Type::argument_list => L!("argument_list"), - Type::job_list => L!("job_list"), +pub fn ast_kind_to_string(k: Kind<'_>) -> &'static wstr { + match k { + Kind::Token(_) => L!("token"), + Kind::Keyword(_) => L!("keyword"), + Kind::Redirection(_) => L!("redirection"), + Kind::VariableAssignment(_) => L!("variable_assignment"), + Kind::VariableAssignmentList(_) => L!("variable_assignment_list"), + Kind::ArgumentOrRedirection(_) => L!("argument_or_redirection"), + Kind::ArgumentOrRedirectionList(_) => L!("argument_or_redirection_list"), + Kind::Statement(_) => L!("statement"), + Kind::JobPipeline(_) => L!("job_pipeline"), + Kind::JobConjunction(_) => L!("job_conjunction"), + Kind::BlockStatementHeader(_) => L!("block_statement_header"), + Kind::ForHeader(_) => L!("for_header"), + Kind::WhileHeader(_) => L!("while_header"), + Kind::FunctionHeader(_) => L!("function_header"), + Kind::BeginHeader(_) => L!("begin_header"), + Kind::BlockStatement(_) => L!("block_statement"), + Kind::BraceStatement(_) => L!("brace_statement"), + Kind::IfClause(_) => L!("if_clause"), + Kind::ElseifClause(_) => L!("elseif_clause"), + Kind::ElseifClauseList(_) => L!("elseif_clause_list"), + Kind::ElseClause(_) => L!("else_clause"), + Kind::IfStatement(_) => L!("if_statement"), + Kind::CaseItem(_) => L!("case_item"), + Kind::SwitchStatement(_) => L!("switch_statement"), + Kind::DecoratedStatement(_) => L!("decorated_statement"), + Kind::NotStatement(_) => L!("not_statement"), + Kind::JobContinuation(_) => L!("job_continuation"), + Kind::JobContinuationList(_) => L!("job_continuation_list"), + Kind::JobConjunctionContinuation(_) => L!("job_conjunction_continuation"), + Kind::AndorJob(_) => L!("andor_job"), + Kind::AndorJobList(_) => L!("andor_job_list"), + Kind::FreestandingArgumentList(_) => L!("freestanding_argument_list"), + Kind::JobConjunctionContinuationList(_) => L!("job_conjunction_continuation_list"), + Kind::MaybeNewlines(_) => L!("maybe_newlines"), + Kind::CaseItemList(_) => L!("case_item_list"), + Kind::Argument(_) => L!("argument"), + Kind::ArgumentList(_) => L!("argument_list"), + Kind::JobList(_) => L!("job_list"), } } @@ -1700,7 +1698,10 @@ pub struct Extras { /// Parse a job list. pub fn parse(src: &wstr, flags: ParseTreeFlags, out_errors: Option<&mut ParseErrorList>) -> Ast { - let mut pops = Populator::new(src, flags, Type::job_list, out_errors); + let mut pops = Populator::new( + src, flags, false, /* not freestanding_arguments */ + out_errors, + ); let mut list = JobList::default(); pops.populate_list(&mut list, true); finalize_parse(pops, list) @@ -1712,7 +1713,10 @@ pub fn parse_argument_list( flags: ParseTreeFlags, out_errors: Option<&mut ParseErrorList>, ) -> Ast { - let mut pops = Populator::new(src, flags, Type::freestanding_argument_list, out_errors); + let mut pops = Populator::new( + src, flags, true, /* freestanding_arguments */ + out_errors, + ); let mut list = FreestandingArgumentList::default(); pops.populate_list(&mut list.arguments, true); finalize_parse(pops, list) @@ -1721,7 +1725,7 @@ pub fn parse_argument_list( // Given that we have populated some top node, add all the extras that we want and produce an Ast. fn finalize_parse(mut pops: Populator<'_>, top: N) -> Ast { // Chomp trailing extras, etc. - pops.chomp_extras(Type::job_list); + pops.chomp_extras(top.kind()); let any_error = pops.any_error; let extras = Extras { @@ -2057,9 +2061,10 @@ struct Populator<'a> { /// Stream of tokens which we consume. tokens: TokenStream<'a>, - /** The type which we are attempting to parse, typically job_list but may be - freestanding_argument_list. */ - top_type: Type, + /// If set, marks that we are parsing a freestanding argument list, e.g. + /// as used in `complete --arguments`. + /// This affects whether argument lists can have semicolons. + freestanding_arguments: bool, /// If set, we are unwinding due to error recovery. unwinding: bool, @@ -2302,19 +2307,21 @@ fn token_types_user_presentable_description(types: &'static [ParseTokenType]) -> } impl<'s> Populator<'s> { - /// Construct from a source, flags, top type, and out_errors, which may be null. + /// Construct a new Populator, parsing source with the given flags. + /// If freestanding_arguments is true, then we are parsing a freestanding argument list + /// as used in `complete --arguments`; this affects whether argument lists can have semicolons. fn new( src: &'s wstr, flags: ParseTreeFlags, - top_type: Type, + freestanding_arguments: bool, out_errors: Option<&'s mut ParseErrorList>, ) -> Self { Self { flags, semis: vec![], errors: vec![], - tokens: TokenStream::new(src, flags, top_type == Type::freestanding_argument_list), - top_type, + tokens: TokenStream::new(src, flags, freestanding_arguments), + freestanding_arguments, unwinding: false, any_error: false, depth: 0, @@ -2353,44 +2360,43 @@ fn allow_incomplete(&self) -> bool { self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) } - /// Return whether a list type `type` allows arbitrary newlines in it. - fn list_type_chomps_newlines(&self, typ: Type) -> bool { - match typ { - Type::argument_list => { - // Hackish. If we are producing a freestanding argument list, then it allows - // semicolons, for hysterical raisins. - self.top_type == Type::freestanding_argument_list + /// Return whether a list kind allows arbitrary newlines in it. + fn list_kind_chomps_newlines(&self, kind: Kind) -> bool { + match kind { + Kind::ArgumentList(_) | Kind::FreestandingArgumentList(_) => { + self.freestanding_arguments } - Type::argument_or_redirection_list => { + + Kind::ArgumentOrRedirectionList(_) => { // No newlines inside arguments. false } - Type::variable_assignment_list => { + Kind::VariableAssignmentList(_) => { // No newlines inside variable assignment lists. false } - Type::job_list => { + Kind::JobList(_) => { // Like echo a \n \n echo b true } - Type::case_item_list => { + Kind::CaseItemList(_) => { // Like switch foo \n \n \n case a \n end true } - Type::andor_job_list => { + Kind::AndorJobList(_) => { // Like while true ; \n \n and true ; end true } - Type::elseif_clause_list => { + Kind::ElseifClauseList(_) => { // Like if true ; \n \n else if false; end true } - Type::job_conjunction_continuation_list => { + Kind::JobConjunctionContinuationList(_) => { // This would be like echo a && echo b \n && echo c // We could conceivably support this but do not now. false } - Type::job_continuation_list => { + Kind::JobContinuationList(_) => { // This would be like echo a \n | echo b // We could conceivably support this but do not now. false @@ -2398,48 +2404,48 @@ fn list_type_chomps_newlines(&self, typ: Type) -> bool { _ => { internal_error!( self, - list_type_chomps_newlines, + list_kind_chomps_newlines, "Type %ls not handled", - ast_type_to_string(typ) + ast_kind_to_string(kind) ); } } } - /// Return whether a list type `type` allows arbitrary semicolons in it. - fn list_type_chomps_semis(&self, typ: Type) -> bool { - match typ { - Type::argument_list => { + /// Return whether a list kind allows arbitrary semicolons in it. + fn list_kind_chomps_semis(&self, kind: Kind) -> bool { + match kind { + Kind::ArgumentList(_) | Kind::FreestandingArgumentList(_) => { // Hackish. If we are producing a freestanding argument list, then it allows // semicolons, for hysterical raisins. // That is, this is OK: complete -c foo -a 'x ; y ; z' // But this is not: foo x ; y ; z - self.top_type == Type::freestanding_argument_list + self.freestanding_arguments } - Type::argument_or_redirection_list | Type::variable_assignment_list => false, - Type::job_list => { + Kind::ArgumentOrRedirectionList(_) | Kind::VariableAssignmentList(_) => false, + Kind::JobList(_) => { // Like echo a ; ; echo b true } - Type::case_item_list => { + Kind::CaseItemList(_) => { // Like switch foo ; ; ; case a \n end // This is historically allowed. true } - Type::andor_job_list => { + Kind::AndorJobList(_) => { // Like while true ; ; ; and true ; end true } - Type::elseif_clause_list => { + Kind::ElseifClauseList(_) => { // Like if true ; ; ; else if false; end false } - Type::job_conjunction_continuation_list => { + Kind::JobConjunctionContinuationList(_) => { // Like echo a ; ; && echo b. Not supported. false } - Type::job_continuation_list => { + Kind::JobContinuationList(_) => { // This would be like echo a ; | echo b // Not supported. // We could conceivably support this but do not now. @@ -2448,18 +2454,18 @@ fn list_type_chomps_semis(&self, typ: Type) -> bool { _ => { internal_error!( self, - list_type_chomps_semis, + list_kind_chomps_semis, "Type %ls not handled", - ast_type_to_string(typ) + ast_kind_to_string(kind) ); } } } - /// Chomp extra comments, semicolons, etc. for a given list type. - fn chomp_extras(&mut self, typ: Type) { - let chomp_semis = self.list_type_chomps_semis(typ); - let chomp_newlines = self.list_type_chomps_newlines(typ); + /// Chomp extra comments, semicolons, etc. for a given list kind. + fn chomp_extras(&mut self, kind: Kind) { + let chomp_semis = self.list_kind_chomps_semis(kind); + let chomp_newlines = self.list_kind_chomps_newlines(kind); loop { let peek = self.tokens.peek(0); if chomp_newlines && peek.typ == ParseTokenType::end && peek.is_newline { @@ -2477,10 +2483,11 @@ fn chomp_extras(&mut self, typ: Type) { } } - /// Return whether a list type should recover from errors.s + /// Return whether a list kind should recover from errors. /// That is, whether we should stop unwinding when we encounter this type. - fn list_type_stops_unwind(&self, typ: Type) -> bool { - typ == Type::job_list && self.flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) + fn list_kind_stops_unwind(&self, kind: Kind) -> bool { + matches!(kind, Kind::JobList(_)) + && self.flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) } /// Return a reference to a non-comment token at index `idx`. @@ -2541,7 +2548,7 @@ fn consume_excess_token_generating_error(&mut self) { // generate a generic error. // TODO: this is a crummy message if we get a tokenizer error, for example: // complete -c foo -a "'abc" - if self.top_type == Type::freestanding_argument_list { + if self.freestanding_arguments { parse_error!( self, tok, @@ -2553,7 +2560,6 @@ fn consume_excess_token_generating_error(&mut self) { return; } - assert!(self.top_type == Type::job_list); match tok.typ { ParseTokenType::string => { // There are three keywords which end a job list. @@ -2652,13 +2658,11 @@ 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(&mut self, list: &mut ListType, exhaust_stream: bool) + fn populate_list(&mut self, list: &mut List, exhaust_stream: bool) where - ContentsType: NodeMut + CheckParse + Default, - ListType: Node + AsMut>, + Contents: NodeMut + CheckParse + Default, + List: Node + Deref> + AsMut>, { - 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. @@ -2673,7 +2677,7 @@ fn populate_list(&mut self, list: &mut ListType, exhaust "%*sunwinding %ls", self.spaces(), "", - ast_type_to_string(typ) + ast_kind_to_string(list.kind()) ); assert!(list.is_empty(), "Should be an empty list"); return; @@ -2686,20 +2690,17 @@ fn populate_list(&mut self, list: &mut ListType, exhaust // 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(typ) { + if !self.list_kind_stops_unwind(list.kind()) { break; } // We are going to stop unwinding. // Rather hackish. Just chomp until we get to a string or end node. loop { let typ = self.peek_type(0); - if [ - ParseTokenType::string, - ParseTokenType::terminate, - ParseTokenType::end, - ] - .contains(&typ) - { + if matches!( + typ, + ParseTokenType::string | ParseTokenType::terminate | ParseTokenType::end + ) { break; } let tok = self.tokens.pop(); @@ -2718,10 +2719,10 @@ fn populate_list(&mut self, list: &mut ListType, exhaust } // Chomp semis and newlines. - self.chomp_extras(typ); + self.chomp_extras(list.kind()); // Now try parsing a node. - if let Some(node) = self.try_parse::() { + if let Some(node) = self.try_parse::() { // #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. @@ -2746,7 +2747,7 @@ fn populate_list(&mut self, list: &mut ListType, exhaust "Contents size out of bounds" ); assert!(list.is_empty(), "List should still be empty"); - *list = contents.into_boxed_slice(); + *list.as_mut() = contents.into_boxed_slice(); } FLOGF!( @@ -2754,7 +2755,7 @@ fn populate_list(&mut self, list: &mut ListType, exhaust "%*s%ls size: %lu", self.spaces(), "", - ast_type_to_string(typ), + ast_kind_to_string(list.kind()), list.len() ); }