From 6e128571cddca5932ee927eadd85ab86d914576a Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 13 Dec 2024 23:48:52 -0600 Subject: [PATCH] fix django block parsing (for now) it's clear i need a rethink of parsing django block tags to nodes, becuase my mental picture of them differs from the actual use of them. E.g. I tend to think of them the same as HTML tags as either "void" tags or ones with end tags and children but not that's not the case at all. Some have intermediate tags (`{% if %}{% else %}{% endif %}`) which is not a huge lift to support, but the mind blowing realization was that it's just convention that the closing tag starts with `end`. for builtin's, yeah that's it's the case that all of the tags that have opening and closing tags all close with a matching tag that is the initial tag with `end` prefixed. but given the flexibility of the django template engine, a third-party could feasibly use *any* closing tag it wanted. that makes it very hard to build a structured, full-featured AST for a Django template. maybe that's not really needed for an LSP. but i'll need to come up with *something* eventually. --- crates/djls-ast/src/parser.rs | 89 ++++++++++--------- ...st__parser__tests__parse_django_block.snap | 7 -- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/crates/djls-ast/src/parser.rs b/crates/djls-ast/src/parser.rs index 7de31ced..ff50e2f3 100644 --- a/crates/djls-ast/src/parser.rs +++ b/crates/djls-ast/src/parser.rs @@ -143,40 +143,52 @@ impl Parser { ))); } - let mut children = Vec::new(); + let mut all_children = Vec::new(); + let mut current_section = Vec::new(); let end_tag = format!("end{}", bits[0]); while !self.is_at_end() { match self.next_node() { Ok(node) => { - children.push(node); + current_section.push(node); } Err(ParserError::ErrorSignal(Signal::ClosingTagFound(tag))) => { - if tag == end_tag { - self.consume()?; - break; - } else if !tag.starts_with("end") { - // For intermediate tags (else, elif, empty, etc.) - self.consume()?; - // Create a new Tag node for the intermediate tag - children.push(Node::Django(DjangoNode::Tag { - kind: DjangoTagKind::from_str(&tag)?, - bits: vec![tag.clone()], - children: Vec::new(), - })); - } else { - return Err(ParserError::ErrorSignal(Signal::ClosingTagFound(tag))); + match tag.as_str() { + tag if tag == end_tag.as_str() => { + // Found matching end tag, complete the block + all_children.extend(current_section); + return Ok(Node::Django(DjangoNode::Tag { + kind, + bits, + children: all_children, + })); + } + tag if !tag.starts_with("end") => { + // Found intermediate tag (like 'else', 'elif') + all_children.extend(current_section); + all_children.push(Node::Django(DjangoNode::Tag { + kind: DjangoTagKind::from_str(tag)?, + bits: vec![tag.to_string()], + children: Vec::new(), + })); + current_section = Vec::new(); + continue; // Continue parsing after intermediate tag + } + tag => { + // Found unexpected end tag + return Err(ParserError::ErrorSignal(Signal::ClosingTagFound( + tag.to_string(), + ))); + } } } - Err(e) => return Err(e), + Err(e) => { + return Err(e); + } } } - Ok(Node::Django(DjangoNode::Tag { - kind, - bits, - children, - })) + Err(ParserError::StreamError(Stream::UnexpectedEof)) } fn parse_django_variable(&mut self, s: &str) -> Result { @@ -460,43 +472,34 @@ impl Parser { } fn synchronize(&mut self) -> Result<(), ParserError> { + println!("--- Starting synchronization ---"); const SYNC_TYPES: &[TokenType] = &[ TokenType::DjangoBlock(String::new()), TokenType::HtmlTagOpen(String::new()), - TokenType::HtmlTagClose(String::new()), // Added TokenType::HtmlTagVoid(String::new()), TokenType::ScriptTagOpen(String::new()), - TokenType::ScriptTagClose(String::new()), // Added TokenType::StyleTagOpen(String::new()), - TokenType::StyleTagClose(String::new()), // Added TokenType::Newline, TokenType::Eof, ]; - let mut nesting = 0; while !self.is_at_end() { - let token = self.peek()?; - match token.token_type() { - TokenType::HtmlTagOpen(_) - | TokenType::ScriptTagOpen(_) - | TokenType::StyleTagOpen(_) => { - nesting += 1; - } - TokenType::HtmlTagClose(_) - | TokenType::ScriptTagClose(_) - | TokenType::StyleTagClose(_) => { - nesting -= 1; - if nesting < 0 { - return Ok(()); - } - } - _ if SYNC_TYPES.contains(token.token_type()) && nesting == 0 => { + let current = self.peek()?; + println!("--- Sync checking token: {:?}", current); + + // Debug print for token type comparison + for sync_type in SYNC_TYPES { + println!("--- Comparing with sync type: {:?}", sync_type); + if matches!(current.token_type(), sync_type) { + println!("--- Found sync point at: {:?}", current); return Ok(()); } - _ => {} } + + println!("--- Consuming token in sync: {:?}", current); self.consume()?; } + println!("--- Reached end during synchronization"); Ok(()) } } diff --git a/crates/djls-ast/src/snapshots/djls_ast__parser__tests__parse_django_block.snap b/crates/djls-ast/src/snapshots/djls_ast__parser__tests__parse_django_block.snap index 237067e7..8124f29d 100644 --- a/crates/djls-ast/src/snapshots/djls_ast__parser__tests__parse_django_block.snap +++ b/crates/djls-ast/src/snapshots/djls_ast__parser__tests__parse_django_block.snap @@ -11,10 +11,3 @@ nodes: - user.is_staff children: - Text: Admin - - Django: - Tag: - kind: Else - bits: - - else - children: - - Text: User