Make ParseTreeFlags an ordinary struct instead of bitflags

Simplifies some code.
This commit is contained in:
Peter Ammon
2026-01-24 13:31:25 -08:00
parent 590ad9cd93
commit a7dc701f26
11 changed files with 108 additions and 108 deletions

View File

@@ -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);
}

View File

@@ -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()
};

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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());

View File

@@ -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() {

View File

@@ -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! {

View File

@@ -204,7 +204,7 @@ pub fn parse_source(
errors: Option<&mut ParseErrorList>,
) -> Option<ParsedSourceRef> {
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)))

View File

@@ -775,14 +775,14 @@ fn compute_indents(src: &wstr, initial_indent: i32) -> Vec<i32> {
// 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);
}

View File

@@ -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() {

View File

@@ -5959,9 +5959,12 @@ struct PositionedToken {
}
fn extract_tokens(s: &wstr) -> Vec<PositionedToken> {
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![];