diff --git a/src/ast.rs b/src/ast.rs index 18c70016e..7d7fde1ee 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1964,9 +1964,7 @@ fn spaces(&self) -> usize { fn status(&mut self) -> ParserStatus { if self.unwinding { ParserStatus::unwinding - } else if self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) - && self.peek_type(0) == ParseTokenType::Terminate - { + } else if self.flags.leave_unterminated && self.peek_type(0) == ParseTokenType::Terminate { ParserStatus::unsourcing } else { ParserStatus::ok @@ -1983,7 +1981,7 @@ fn unsource_leaves(&mut self) -> bool { /// Return whether we permit an incomplete parse tree. fn allow_incomplete(&self) -> bool { - self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) + self.flags.leave_unterminated } /// Return whether a list kind allows arbitrary newlines in it. @@ -2100,7 +2098,7 @@ fn chomp_extras(&mut self, kind: Kind) { } else if chomp_semis && peek.typ == ParseTokenType::End && !peek.is_newline { let tok = self.tokens.pop(); // Perhaps save this extra semi. - if self.flags.contains(ParseTreeFlags::SHOW_EXTRA_SEMIS) { + if self.flags.show_extra_semis { self.semis.push(tok.range()); } } else { @@ -2112,8 +2110,7 @@ fn chomp_extras(&mut self, kind: Kind) { /// Return whether a list kind should recover from errors. /// That is, whether we should stop unwinding when we encounter this type. fn list_kind_stops_unwind(&self, kind: Kind) -> bool { - matches!(kind, Kind::JobList(_)) - && self.flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) + matches!(kind, Kind::JobList(_)) && self.flags.continue_after_error } /// Return a reference to a non-comment token at index `idx`. @@ -2643,12 +2640,11 @@ fn visit_token(&mut self, token: &mut dyn Token) { } if !token.allows_token(self.peek_token(0).typ) { - if self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) - && [ - TokenizerError::UnterminatedQuote, - TokenizerError::UnterminatedSubshell, - ] - .contains(&self.peek_token(0).tok_error) + if self.flags.leave_unterminated + && matches!( + self.peek_token(0).tok_error, + TokenizerError::UnterminatedQuote | TokenizerError::UnterminatedSubshell + ) { return; } @@ -2679,12 +2675,11 @@ fn visit_keyword(&mut self, keyword: &mut dyn Keyword) -> VisitResult { if !keyword.allows_keyword(self.peek_token(0).keyword) { *keyword.range_mut() = None; - if self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) - && [ - TokenizerError::UnterminatedQuote, - TokenizerError::UnterminatedSubshell, - ] - .contains(&self.peek_token(0).tok_error) + if self.flags.leave_unterminated + && matches!( + self.peek_token(0).tok_error, + TokenizerError::UnterminatedQuote | TokenizerError::UnterminatedSubshell + ) { return VisitResult::Continue(()); } @@ -2754,13 +2749,13 @@ fn from(flags: ParseTreeFlags) -> Self { let mut tok_flags = TokFlags(0); // Note we do not need to respect parse_flag_show_blank_lines, no clients are interested // in them. - if flags.contains(ParseTreeFlags::INCLUDE_COMMENTS) { + if flags.include_comments { tok_flags |= TOK_SHOW_COMMENTS; } - if flags.contains(ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS) { + if flags.accept_incomplete_tokens { tok_flags |= TOK_ACCEPT_UNFINISHED; } - if flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) { + if flags.continue_after_error { tok_flags |= TOK_CONTINUE_AFTER_ERROR } tok_flags @@ -2837,7 +2832,7 @@ mod tests { fn test_ast_parse() { let _cleanup = test_init(); let src = L!("echo"); - let ast = ast::parse(src, ParseTreeFlags::empty(), None); + let ast = ast::parse(src, ParseTreeFlags::default(), None); assert!(!ast.any_error); } diff --git a/src/bin/fish.rs b/src/bin/fish.rs index 042a8f3bb..6d1feee24 100644 --- a/src/bin/fish.rs +++ b/src/bin/fish.rs @@ -193,7 +193,7 @@ fn run_command_list(parser: &Parser, cmds: &[OsString]) -> Result<(), libc::c_in let cmd_wcs = bytes2wcstring(cmd.as_bytes()); let mut errors = ParseErrorList::new(); - let ast = ast::parse(&cmd_wcs, ParseTreeFlags::empty(), Some(&mut errors)); + let ast = ast::parse(&cmd_wcs, ParseTreeFlags::default(), Some(&mut errors)); let errored = ast.errored() || { parse_util_detect_errors_in_ast(&ast, &cmd_wcs, Some(&mut errors)).is_err() }; diff --git a/src/builtins/commandline.rs b/src/builtins/commandline.rs index d3e793952..d5cb129c2 100644 --- a/src/builtins/commandline.rs +++ b/src/builtins/commandline.rs @@ -103,11 +103,12 @@ fn strip_dollar_prefixes(insert_mode: AppendMode, prefix: &wstr, insert: &wstr) } insert.find(L!("$ "))?; // Early return. let source = prefix.to_owned() + insert; - let ast = ast::parse( - &source, - ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS | ParseTreeFlags::LEAVE_UNTERMINATED, - None, - ); + let flags = ParseTreeFlags { + accept_incomplete_tokens: true, + leave_unterminated: true, + ..Default::default() + }; + let ast = ast::parse(&source, flags, None); let mut stripped = WString::new(); let mut have = prefix.len(); for node in ast.walk() { diff --git a/src/builtins/fish_indent.rs b/src/builtins/fish_indent.rs index c75436041..4f4b39e45 100644 --- a/src/builtins/fish_indent.rs +++ b/src/builtins/fish_indent.rs @@ -896,10 +896,13 @@ fn prettify_traversal(&mut self) { // The flags we use to parse. fn parse_flags() -> ParseTreeFlags { - ParseTreeFlags::CONTINUE_AFTER_ERROR - | ParseTreeFlags::INCLUDE_COMMENTS - | ParseTreeFlags::LEAVE_UNTERMINATED - | ParseTreeFlags::SHOW_BLANK_LINES + ParseTreeFlags { + continue_after_error: true, + include_comments: true, + leave_unterminated: true, + show_blank_lines: true, + ..Default::default() + } } /// Return whether a character at a given index is escaped. @@ -1304,13 +1307,13 @@ struct TokenRange { // Entry point for prettification. fn prettify(streams: &mut IoStreams, src: &wstr, do_indent: bool) -> WString { if DUMP_PARSE_TREE.load() { - let ast = ast::parse( - src, - ParseTreeFlags::LEAVE_UNTERMINATED - | ParseTreeFlags::INCLUDE_COMMENTS - | ParseTreeFlags::SHOW_EXTRA_SEMIS, - None, - ); + let flags = ParseTreeFlags { + leave_unterminated: true, + include_comments: true, + show_extra_semis: true, + ..Default::default() + }; + let ast = ast::parse(src, flags, None); let ast_dump = ast.dump(src); streams.err.appendln(ast_dump); diff --git a/src/highlight/highlight.rs b/src/highlight/highlight.rs index 95002cefa..4a0c0c7ad 100644 --- a/src/highlight/highlight.rs +++ b/src/highlight/highlight.rs @@ -300,11 +300,12 @@ fn autosuggest_parse_command( buff: &wstr, ctx: &OperationContext<'_>, ) -> Option<(WString, WString)> { - let ast = ast::parse( - buff, - ParseTreeFlags::CONTINUE_AFTER_ERROR | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS, - None, - ); + let flags = ParseTreeFlags { + continue_after_error: true, + accept_incomplete_tokens: true, + ..Default::default() + }; + let ast = ast::parse(buff, flags, None); // Find the first statement. let job_list: &ast::JobList = ast.top(); @@ -742,11 +743,14 @@ pub fn highlight(&mut self) -> ColorArray { .resize(self.buff.len(), HighlightSpec::default()); // Flags we use for AST parsing. - let ast_flags = ParseTreeFlags::CONTINUE_AFTER_ERROR - | ParseTreeFlags::INCLUDE_COMMENTS - | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS - | ParseTreeFlags::LEAVE_UNTERMINATED - | ParseTreeFlags::SHOW_EXTRA_SEMIS; + let ast_flags = ParseTreeFlags { + continue_after_error: true, + include_comments: true, + accept_incomplete_tokens: true, + leave_unterminated: true, + show_extra_semis: true, + ..Default::default() + }; let ast = ast::parse(self.buff, ast_flags, None); self.visit_children(ast.top()); diff --git a/src/history/history.rs b/src/history/history.rs index 737549e11..5648a776d 100644 --- a/src/history/history.rs +++ b/src/history/history.rs @@ -1179,7 +1179,7 @@ fn should_import_bash_history_line(line: &wstr) -> bool { } } - if ast::parse(line, ParseTreeFlags::empty(), None).errored() { + if ast::parse(line, ParseTreeFlags::default(), None).errored() { return false; } @@ -1264,7 +1264,7 @@ pub fn add_pending_with_file_detection( // Find all arguments that look like they could be file paths. let mut needs_sync_write = false; - let ast = ast::parse(s, ParseTreeFlags::empty(), None); + let ast = ast::parse(s, ParseTreeFlags::default(), None); let mut potential_paths = Vec::new(); for node in ast.walk() { diff --git a/src/parse_constants.rs b/src/parse_constants.rs index 2da3ddfe6..96af0b95b 100644 --- a/src/parse_constants.rs +++ b/src/parse_constants.rs @@ -9,24 +9,22 @@ pub const SOURCE_OFFSET_INVALID: usize = SourceOffset::MAX as _; pub const SOURCE_LOCATION_UNKNOWN: usize = usize::MAX; -bitflags! { - #[derive(Copy, Clone, Default)] - pub struct ParseTreeFlags: u8 { - /// attempt to build a "parse tree" no matter what. this may result in a 'forest' of - /// disconnected trees. this is intended to be used by syntax highlighting. - const CONTINUE_AFTER_ERROR = 1 << 0; - /// include comment tokens. - const INCLUDE_COMMENTS = 1 << 1; - /// indicate that the tokenizer should accept incomplete tokens - const ACCEPT_INCOMPLETE_TOKENS = 1 << 2; - /// indicate that the parser should not generate the terminate token, allowing an 'unfinished' - /// tree where some nodes may have no productions. - const LEAVE_UNTERMINATED = 1 << 3; - /// indicate that the parser should generate job_list entries for blank lines. - const SHOW_BLANK_LINES = 1 << 4; - /// indicate that extra semis should be generated. - const SHOW_EXTRA_SEMIS = 1 << 5; - } +#[derive(Copy, Clone, Default)] +pub struct ParseTreeFlags { + /// attempt to build a "parse tree" no matter what. this may result in a 'forest' of + /// disconnected trees. this is intended to be used by syntax highlighting. + pub continue_after_error: bool, + /// include comment tokens. + pub include_comments: bool, + /// indicate that the tokenizer should accept incomplete tokens + pub accept_incomplete_tokens: bool, + /// indicate that the parser should not generate the terminate token, allowing an 'unfinished' + /// tree where some nodes may have no productions. + pub leave_unterminated: bool, + /// indicate that the parser should generate job_list entries for blank lines. + pub show_blank_lines: bool, + /// indicate that extra semis should be generated. + pub show_extra_semis: bool, } bitflags! { diff --git a/src/parse_tree.rs b/src/parse_tree.rs index f78940645..27f7ad6f1 100644 --- a/src/parse_tree.rs +++ b/src/parse_tree.rs @@ -204,7 +204,7 @@ pub fn parse_source( errors: Option<&mut ParseErrorList>, ) -> Option { let ast = ast::parse(&src, flags, errors); - if ast.errored() && !flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) { + if ast.errored() && !flags.continue_after_error { None } else { Some(Arc::new(ParsedSource::new(src, ast))) diff --git a/src/parse_util.rs b/src/parse_util.rs index 95863fcbc..96b920989 100644 --- a/src/parse_util.rs +++ b/src/parse_util.rs @@ -775,14 +775,14 @@ fn compute_indents(src: &wstr, initial_indent: i32) -> Vec { // the last node we visited becomes the input indent of the next. I.e. in the case of 'switch // foo ; cas', we get an invalid parse tree (since 'cas' is not valid) but we indent it as if it // were a case item list. - let ast = ast::parse( - src, - ParseTreeFlags::CONTINUE_AFTER_ERROR - | ParseTreeFlags::INCLUDE_COMMENTS - | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS - | ParseTreeFlags::LEAVE_UNTERMINATED, - None, - ); + let flags = ParseTreeFlags { + continue_after_error: true, + include_comments: true, + accept_incomplete_tokens: true, + leave_unterminated: true, + ..Default::default() + }; + let ast = ast::parse(src, flags, None); { let mut iv = IndentVisitor::new(src, &mut indents, initial_indent); iv.visit(ast.top()); @@ -1133,10 +1133,9 @@ pub fn parse_util_detect_errors( // allow_incomplete is set. let mut has_unclosed_quote_or_subshell = false; - let parse_flags = if allow_incomplete { - ParseTreeFlags::LEAVE_UNTERMINATED - } else { - ParseTreeFlags::empty() + let parse_flags = ParseTreeFlags { + leave_unterminated: allow_incomplete, + ..Default::default() }; // Parse the input string into an ast. Some errors are detected here. @@ -1336,7 +1335,7 @@ pub fn parse_util_detect_errors_in_argument_list( // Parse the string as a freestanding argument list. let mut errors = ParseErrorList::new(); - let ast = ast::parse_argument_list(arg_list_src, ParseTreeFlags::empty(), Some(&mut errors)); + let ast = ast::parse_argument_list(arg_list_src, ParseTreeFlags::default(), Some(&mut errors)); if !errors.is_empty() { return get_error_text(&errors); } diff --git a/src/parser.rs b/src/parser.rs index a26f40a0c..02cbf9c79 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -528,7 +528,7 @@ pub fn eval_with( let mut error_list = ParseErrorList::new(); if let Some(ps) = parse_source( cmd.to_owned(), - ParseTreeFlags::empty(), + ParseTreeFlags::default(), Some(&mut error_list), ) { return self.eval_parsed_source( @@ -600,7 +600,7 @@ pub fn eval_wstr( use crate::parse_tree::ParsedSource; use crate::parse_util::parse_util_detect_errors_in_ast; let mut errors = vec![]; - let ast = ast::parse(&src, ParseTreeFlags::empty(), Some(&mut errors)); + let ast = ast::parse(&src, ParseTreeFlags::default(), Some(&mut errors)); let mut errored = ast.errored(); if !errored { errored = parse_util_detect_errors_in_ast(&ast, &src, Some(&mut errors)).is_err(); @@ -2051,18 +2051,19 @@ fn test_new_parser_ad_hoc() { let ast = ast::parse(L!("a="), ParseTreeFlags::default(), None); assert!(ast.errored()); + let flags = ParseTreeFlags { + leave_unterminated: true, + ..ParseTreeFlags::default() + }; + // If we are leaving things unterminated, this should not produce an error. // i.e. when typing "a=" at the command line, it should be treated as valid // because we don't want to color it as an error. - let ast = ast::parse(L!("a="), ParseTreeFlags::LEAVE_UNTERMINATED, None); + let ast = ast::parse(L!("a="), flags, None); assert!(!ast.errored()); let mut errors = vec![]; - ast::parse( - L!("begin; echo ("), - ParseTreeFlags::LEAVE_UNTERMINATED, - Some(&mut errors), - ); + ast::parse(L!("begin; echo ("), flags, Some(&mut errors)); assert_eq!(errors.len(), 1); assert_eq!( errors[0].code, @@ -2070,11 +2071,7 @@ fn test_new_parser_ad_hoc() { ); errors.clear(); - ast::parse( - L!("for x in ("), - ParseTreeFlags::LEAVE_UNTERMINATED, - Some(&mut errors), - ); + ast::parse(L!("for x in ("), flags, Some(&mut errors)); assert_eq!(errors.len(), 1); assert_eq!( errors[0].code, @@ -2082,11 +2079,7 @@ fn test_new_parser_ad_hoc() { ); errors.clear(); - ast::parse( - L!("begin; echo '"), - ParseTreeFlags::LEAVE_UNTERMINATED, - Some(&mut errors), - ); + ast::parse(L!("begin; echo '"), flags, Some(&mut errors)); assert_eq!(errors.len(), 1); assert_eq!(errors[0].code, ParseErrorCode::TokenizerUnterminatedQuote); } @@ -2395,7 +2388,11 @@ fn get_parents<'s>( #[test] fn test_ast() { // Light testing of the AST and traversals. - let ast = ast::parse(TrueSemiAstTester::TRUE_SEMI, ParseTreeFlags::empty(), None); + let ast = ast::parse( + TrueSemiAstTester::TRUE_SEMI, + ParseTreeFlags::default(), + None, + ); let tester = TrueSemiAstTester::new(&ast); // Walk the AST and collect all nodes. @@ -2456,7 +2453,7 @@ fn test_ast() { #[should_panic] fn test_traversal_skip_children_panics() { // Test that we panic if we try to skip children of a node that is not the current node. - let ast = ast::parse(L!("true;"), ParseTreeFlags::empty(), None); + let ast = ast::parse(L!("true;"), ParseTreeFlags::default(), None); let mut traversal = ast.walk(); while let Some(node) = traversal.next() { if matches!(node.kind(), ast::Kind::DecoratedStatement(_)) { @@ -2470,7 +2467,7 @@ fn test_traversal_skip_children_panics() { #[should_panic] fn test_traversal_parent_panics() { // Can only get the parent of nodes still on the stack. - let ast = ast::parse(L!("true;"), ParseTreeFlags::empty(), None); + let ast = ast::parse(L!("true;"), ParseTreeFlags::default(), None); let mut traversal = ast.walk(); let mut decorated_statement = None; while let Some(node) = traversal.next() { diff --git a/src/reader/reader.rs b/src/reader/reader.rs index 36f3858ee..e2e7bd01b 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -5959,9 +5959,12 @@ struct PositionedToken { } fn extract_tokens(s: &wstr) -> Vec { - let ast_flags = ParseTreeFlags::CONTINUE_AFTER_ERROR - | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS - | ParseTreeFlags::LEAVE_UNTERMINATED; + let ast_flags = ParseTreeFlags { + continue_after_error: true, + accept_incomplete_tokens: true, + leave_unterminated: true, + ..ParseTreeFlags::default() + }; let ast = ast::parse(s, ast_flags, None); let mut result = vec![];