diff --git a/fish-rust/src/ast.rs b/fish-rust/src/ast.rs index 95c46383a..e1a00f3f4 100644 --- a/fish-rust/src/ast.rs +++ b/fish-rust/src/ast.rs @@ -14,9 +14,7 @@ use crate::flog::FLOG; use crate::parse_constants::{ token_type_user_presentable_description, ParseError, ParseErrorCode, ParseErrorList, ParseErrorListFfi, ParseKeyword, ParseTokenType, ParseTreeFlags, SourceRange, - StatementDecoration, INVALID_PIPELINE_CMD_ERR_MSG, PARSE_FLAG_ACCEPT_INCOMPLETE_TOKENS, - PARSE_FLAG_CONTINUE_AFTER_ERROR, PARSE_FLAG_INCLUDE_COMMENTS, PARSE_FLAG_LEAVE_UNTERMINATED, - PARSE_FLAG_SHOW_EXTRA_SEMIS, SOURCE_OFFSET_INVALID, + StatementDecoration, INVALID_PIPELINE_CMD_ERR_MSG, SOURCE_OFFSET_INVALID, }; use crate::parse_tree::ParseToken; use crate::tokenizer::{ @@ -2937,7 +2935,7 @@ impl<'s> Populator<'s> { fn status(&mut self) -> ParserStatus { if self.unwinding { ParserStatus::unwinding - } else if self.flags & PARSE_FLAG_LEAVE_UNTERMINATED + } else if self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) && self.peek_type(0) == ParseTokenType::terminate { ParserStatus::unsourcing @@ -2956,7 +2954,7 @@ impl<'s> Populator<'s> { /// \return whether we permit an incomplete parse tree. fn allow_incomplete(&self) -> bool { - self.flags & PARSE_FLAG_LEAVE_UNTERMINATED + self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) } /// \return whether a list type \p type allows arbitrary newlines in it. @@ -3074,7 +3072,7 @@ impl<'s> Populator<'s> { } else if chomp_semis && peek.typ == ParseTokenType::end && !peek.is_newline { let tok = self.tokens.pop(); // Perhaps save this extra semi. - if self.flags & PARSE_FLAG_SHOW_EXTRA_SEMIS { + if self.flags.contains(ParseTreeFlags::SHOW_EXTRA_SEMIS) { self.semis.push(tok.range()); } } else { @@ -3086,7 +3084,7 @@ impl<'s> Populator<'s> { /// \return whether a list type should recover from errors.s /// 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 & PARSE_FLAG_CONTINUE_AFTER_ERROR + typ == Type::job_list && self.flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) } /// \return a reference to a non-comment token at index \p idx. @@ -3678,7 +3676,7 @@ impl<'s> Populator<'s> { } if !token.allows_token(self.peek_token(0).typ) { - if self.flags & PARSE_FLAG_LEAVE_UNTERMINATED + if self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) && [ TokenizerError::unterminated_quote, TokenizerError::unterminated_subshell, @@ -3714,7 +3712,7 @@ impl<'s> Populator<'s> { if !keyword.allows_keyword(self.peek_token(0).keyword) { *keyword.range_mut() = None; - if self.flags & PARSE_FLAG_LEAVE_UNTERMINATED + if self.flags.contains(ParseTreeFlags::LEAVE_UNTERMINATED) && [ TokenizerError::unterminated_quote, TokenizerError::unterminated_subshell, @@ -3842,13 +3840,13 @@ impl From for TokFlags { 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 & PARSE_FLAG_INCLUDE_COMMENTS { + if flags.contains(ParseTreeFlags::INCLUDE_COMMENTS) { tok_flags |= TOK_SHOW_COMMENTS; } - if flags & PARSE_FLAG_ACCEPT_INCOMPLETE_TOKENS { + if flags.contains(ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS) { tok_flags |= TOK_ACCEPT_UNFINISHED; } - if flags & PARSE_FLAG_CONTINUE_AFTER_ERROR { + if flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) { tok_flags |= TOK_CONTINUE_AFTER_ERROR } tok_flags @@ -3921,9 +3919,8 @@ fn keyword_for_token(tok: TokenType, token: &wstr) -> ParseKeyword { use crate::ffi_tests::add_test; add_test!("test_ast_parse", || { - use crate::parse_constants::PARSE_FLAG_NONE; let src = L!("echo"); - let ast = Ast::parse(src, PARSE_FLAG_NONE, None); + let ast = Ast::parse(src, ParseTreeFlags::empty(), None); assert!(!ast.any_error); }); @@ -4422,7 +4419,7 @@ impl Ast { fn ast_parse_ffi(src: &CxxWString, flags: u8, errors: *mut ParseErrorListFfi) -> Box { Box::new(Ast::parse( src.as_wstr(), - ParseTreeFlags(flags), + ParseTreeFlags::from_bits(flags).unwrap(), if errors.is_null() { None } else { @@ -4438,7 +4435,7 @@ fn ast_parse_argument_list_ffi( ) -> Box { Box::new(Ast::parse_argument_list( src.as_wstr(), - ParseTreeFlags(flags), + ParseTreeFlags::from_bits(flags).unwrap(), if errors.is_null() { None } else { diff --git a/fish-rust/src/parse_constants.rs b/fish-rust/src/parse_constants.rs index ec9ed4c85..3c2a785d6 100644 --- a/fish-rust/src/parse_constants.rs +++ b/fish-rust/src/parse_constants.rs @@ -5,9 +5,9 @@ use crate::tokenizer::variable_assignment_equals_pos; use crate::wchar::{wstr, WString, L}; use crate::wchar_ffi::{wcharz, AsWstr, WCharFromFFI, WCharToFFI}; use crate::wutil::{sprintf, wgettext_fmt}; +use bitflags::bitflags; use cxx::{type_id, ExternType}; use cxx::{CxxWString, UniquePtr}; -use std::ops::{BitAnd, BitOr, BitOrAssign}; use widestring_suffix::widestrs; pub type SourceOffset = u32; @@ -15,58 +15,30 @@ pub type SourceOffset = u32; pub const SOURCE_OFFSET_INVALID: usize = SourceOffset::MAX as _; pub const SOURCE_LOCATION_UNKNOWN: usize = usize::MAX; -#[derive(Copy, Clone)] -pub struct ParseTreeFlags(pub u8); - -pub const PARSE_FLAG_NONE: ParseTreeFlags = ParseTreeFlags(0); -/// 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 const PARSE_FLAG_CONTINUE_AFTER_ERROR: ParseTreeFlags = ParseTreeFlags(1 << 0); -/// include comment tokens. -pub const PARSE_FLAG_INCLUDE_COMMENTS: ParseTreeFlags = ParseTreeFlags(1 << 1); -/// indicate that the tokenizer should accept incomplete tokens */ -pub const PARSE_FLAG_ACCEPT_INCOMPLETE_TOKENS: ParseTreeFlags = ParseTreeFlags(1 << 2); -/// indicate that the parser should not generate the terminate token, allowing an 'unfinished' -/// tree where some nodes may have no productions. -pub const PARSE_FLAG_LEAVE_UNTERMINATED: ParseTreeFlags = ParseTreeFlags(1 << 3); -/// indicate that the parser should generate job_list entries for blank lines. -pub const PARSE_FLAG_SHOW_BLANK_LINES: ParseTreeFlags = ParseTreeFlags(1 << 4); -/// indicate that extra semis should be generated. -pub const PARSE_FLAG_SHOW_EXTRA_SEMIS: ParseTreeFlags = ParseTreeFlags(1 << 5); - -impl BitAnd for ParseTreeFlags { - type Output = bool; - fn bitand(self, rhs: Self) -> Self::Output { - (self.0 & rhs.0) != 0 - } -} -impl BitOr for ParseTreeFlags { - type Output = ParseTreeFlags; - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} -impl BitOrAssign for ParseTreeFlags { - fn bitor_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 +bitflags! { + 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(PartialEq, Eq, Copy, Clone, Default)] -pub struct ParserTestErrorBits(u8); - -pub const PARSER_TEST_ERROR: ParserTestErrorBits = ParserTestErrorBits(1); -pub const PARSER_TEST_INCOMPLETE: ParserTestErrorBits = ParserTestErrorBits(2); - -impl BitAnd for ParserTestErrorBits { - type Output = bool; - fn bitand(self, rhs: Self) -> Self::Output { - (self.0 & rhs.0) != 0 - } -} -impl BitOrAssign for ParserTestErrorBits { - fn bitor_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 +bitflags! { + #[derive(Default)] + pub struct ParserTestErrorBits: u8 { + const ERROR = 1; + const INCOMPLETE = 2; } } diff --git a/fish-rust/src/parse_tree.rs b/fish-rust/src/parse_tree.rs index 809ec3fb8..d68a67116 100644 --- a/fish-rust/src/parse_tree.rs +++ b/fish-rust/src/parse_tree.rs @@ -6,8 +6,7 @@ use std::rc::Rc; use crate::ast::Ast; use crate::parse_constants::{ token_type_user_presentable_description, ParseErrorCode, ParseErrorList, ParseErrorListFfi, - ParseKeyword, ParseTokenType, ParseTreeFlags, SourceOffset, SourceRange, - PARSE_FLAG_CONTINUE_AFTER_ERROR, SOURCE_OFFSET_INVALID, + ParseKeyword, ParseTokenType, ParseTreeFlags, SourceOffset, SourceRange, SOURCE_OFFSET_INVALID, }; use crate::tokenizer::TokenizerError; use crate::wchar::{wstr, WString, L}; @@ -123,7 +122,7 @@ pub fn parse_source( errors: Option<&mut ParseErrorList>, ) -> ParsedSourceRef { let ast = Ast::parse(&src, flags, errors); - if ast.errored() && !(flags & PARSE_FLAG_CONTINUE_AFTER_ERROR) { + if ast.errored() && !flags.contains(ParseTreeFlags::CONTINUE_AFTER_ERROR) { None } else { Some(Rc::new(ParsedSource::new(src, ast))) @@ -179,7 +178,7 @@ fn parse_source_ffi( ) -> Box { Box::new(ParsedSourceRefFFI(parse_source( src.from_ffi(), - ParseTreeFlags(flags), + ParseTreeFlags::from_bits(flags).unwrap(), if errors.is_null() { None } else { diff --git a/fish-rust/src/parse_util.rs b/fish-rust/src/parse_util.rs index 6bce77f88..d938aa020 100644 --- a/fish-rust/src/parse_util.rs +++ b/fish-rust/src/parse_util.rs @@ -14,14 +14,11 @@ use crate::future_feature_flags::{feature_test, FeatureFlag}; use crate::operation_context::OperationContext; use crate::parse_constants::{ parse_error_offset_source_start, ParseError, ParseErrorCode, ParseErrorList, ParseKeyword, - ParseTokenType, ParserTestErrorBits, PipelinePosition, StatementDecoration, + ParseTokenType, ParseTreeFlags, ParserTestErrorBits, PipelinePosition, StatementDecoration, ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE1, ERROR_BRACKETED_VARIABLE_QUOTED1, ERROR_NOT_ARGV_AT, ERROR_NOT_ARGV_COUNT, ERROR_NOT_ARGV_STAR, ERROR_NOT_PID, ERROR_NOT_STATUS, ERROR_NO_VAR_NAME, INVALID_BREAK_ERR_MSG, INVALID_CONTINUE_ERR_MSG, - INVALID_PIPELINE_CMD_ERR_MSG, PARSER_TEST_ERROR, PARSER_TEST_INCOMPLETE, - PARSE_FLAG_ACCEPT_INCOMPLETE_TOKENS, PARSE_FLAG_CONTINUE_AFTER_ERROR, - PARSE_FLAG_INCLUDE_COMMENTS, PARSE_FLAG_LEAVE_UNTERMINATED, PARSE_FLAG_NONE, - UNKNOWN_BUILTIN_ERR_MSG, + INVALID_PIPELINE_CMD_ERR_MSG, UNKNOWN_BUILTIN_ERR_MSG, }; use crate::tokenizer::{ comment_end, is_token_delimiter, quote_end, Tok, TokenType, Tokenizer, TOK_ACCEPT_UNFINISHED, @@ -742,10 +739,10 @@ pub fn parse_util_compute_indents(src: &wstr) -> Vec { // were a case item list. let ast = Ast::parse( src, - PARSE_FLAG_CONTINUE_AFTER_ERROR - | PARSE_FLAG_INCLUDE_COMMENTS - | PARSE_FLAG_ACCEPT_INCOMPLETE_TOKENS - | PARSE_FLAG_LEAVE_UNTERMINATED, + ParseTreeFlags::CONTINUE_AFTER_ERROR + | ParseTreeFlags::INCLUDE_COMMENTS + | ParseTreeFlags::ACCEPT_INCOMPLETE_TOKENS + | ParseTreeFlags::LEAVE_UNTERMINATED, None, ); { @@ -965,7 +962,7 @@ impl<'a> NodeVisitor<'a> for IndentVisitor<'a> { } /// Given a string, detect parse errors in it. If allow_incomplete is set, then if the string is -/// incomplete (e.g. an unclosed quote), an error is not returned and the PARSER_TEST_INCOMPLETE bit +/// incomplete (e.g. an unclosed quote), an error is not returned and the ParserTestErrorBits::INCOMPLETE bit /// is set in the return value. If allow_incomplete is not set, then incomplete strings result in an /// error. pub fn parse_util_detect_errors( @@ -978,9 +975,9 @@ pub fn parse_util_detect_errors( let mut has_unclosed_quote_or_subshell = false; let parse_flags = if allow_incomplete { - PARSE_FLAG_LEAVE_UNTERMINATED + ParseTreeFlags::LEAVE_UNTERMINATED } else { - PARSE_FLAG_NONE + ParseTreeFlags::empty() }; // Parse the input string into an ast. Some errors are detected here. @@ -1009,14 +1006,14 @@ pub fn parse_util_detect_errors( assert!(!has_unclosed_quote_or_subshell || allow_incomplete); if has_unclosed_quote_or_subshell { // We do not bother to validate the rest of the tree in this case. - return Err(PARSER_TEST_INCOMPLETE); + return Err(ParserTestErrorBits::INCOMPLETE); } // Early parse error, stop here. if !parse_errors.is_empty() { if let Some(errors) = out_errors.as_mut() { errors.extend(parse_errors.into_iter()); - return Err(PARSER_TEST_ERROR); + return Err(ParserTestErrorBits::ERROR); } } @@ -1107,11 +1104,11 @@ pub fn parse_util_detect_errors_in_ast( } if errored { - res |= PARSER_TEST_ERROR; + res |= ParserTestErrorBits::ERROR; } if has_unclosed_block || has_unclosed_pipe || has_unclosed_conjunction { - res |= PARSER_TEST_INCOMPLETE; + res |= ParserTestErrorBits::INCOMPLETE; } if res == ParserTestErrorBits::default() { Ok(()) @@ -1139,7 +1136,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, PARSE_FLAG_NONE, Some(&mut errors)); + let ast = Ast::parse_argument_list(arg_list_src, ParseTreeFlags::empty(), Some(&mut errors)); if !errors.is_empty() { return get_error_text(&errors); } @@ -1219,13 +1216,13 @@ pub fn parse_util_detect_errors_in_argument( append_syntax_error!( out_errors, source_start + begin, end - begin, "Incomplete escape sequence '%ls'", arg_src); - return PARSER_TEST_ERROR; + return ParserTestErrorBits::ERROR; } append_syntax_error!( out_errors, source_start + begin, end - begin, "Invalid token '%ls'", arg_src); } - return PARSER_TEST_ERROR; + return ParserTestErrorBits::ERROR; }; let mut err = ParserTestErrorBits::default(); @@ -1239,7 +1236,7 @@ pub fn parse_util_detect_errors_in_argument( if ![VARIABLE_EXPAND, VARIABLE_EXPAND_SINGLE, '('].contains(&next_char) && !valid_var_name_char(next_char) { - err = PARSER_TEST_ERROR; + err = ParserTestErrorBits::ERROR; if let Some(ref mut out_errors) = out_errors { let mut first_dollar = idx; while first_dollar > 0 @@ -1282,7 +1279,7 @@ pub fn parse_util_detect_errors_in_argument( Some(&mut has_dollar), ) { -1 => { - err |= PARSER_TEST_ERROR; + err |= ParserTestErrorBits::ERROR; append_syntax_error!(out_errors, source_start, 1, "Mismatched parenthesis"); return err; }