Skip to content

Commit b1b3e6a

Browse files
fix django block parsing (for now) (#31)
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.
1 parent a9fc082 commit b1b3e6a

File tree

2 files changed

+46
-50
lines changed

2 files changed

+46
-50
lines changed

crates/djls-ast/src/parser.rs

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -143,40 +143,52 @@ impl Parser {
143143
)));
144144
}
145145

146-
let mut children = Vec::new();
146+
let mut all_children = Vec::new();
147+
let mut current_section = Vec::new();
147148
let end_tag = format!("end{}", bits[0]);
148149

149150
while !self.is_at_end() {
150151
match self.next_node() {
151152
Ok(node) => {
152-
children.push(node);
153+
current_section.push(node);
153154
}
154155
Err(ParserError::ErrorSignal(Signal::ClosingTagFound(tag))) => {
155-
if tag == end_tag {
156-
self.consume()?;
157-
break;
158-
} else if !tag.starts_with("end") {
159-
// For intermediate tags (else, elif, empty, etc.)
160-
self.consume()?;
161-
// Create a new Tag node for the intermediate tag
162-
children.push(Node::Django(DjangoNode::Tag {
163-
kind: DjangoTagKind::from_str(&tag)?,
164-
bits: vec![tag.clone()],
165-
children: Vec::new(),
166-
}));
167-
} else {
168-
return Err(ParserError::ErrorSignal(Signal::ClosingTagFound(tag)));
156+
match tag.as_str() {
157+
tag if tag == end_tag.as_str() => {
158+
// Found matching end tag, complete the block
159+
all_children.extend(current_section);
160+
return Ok(Node::Django(DjangoNode::Tag {
161+
kind,
162+
bits,
163+
children: all_children,
164+
}));
165+
}
166+
tag if !tag.starts_with("end") => {
167+
// Found intermediate tag (like 'else', 'elif')
168+
all_children.extend(current_section);
169+
all_children.push(Node::Django(DjangoNode::Tag {
170+
kind: DjangoTagKind::from_str(tag)?,
171+
bits: vec![tag.to_string()],
172+
children: Vec::new(),
173+
}));
174+
current_section = Vec::new();
175+
continue; // Continue parsing after intermediate tag
176+
}
177+
tag => {
178+
// Found unexpected end tag
179+
return Err(ParserError::ErrorSignal(Signal::ClosingTagFound(
180+
tag.to_string(),
181+
)));
182+
}
169183
}
170184
}
171-
Err(e) => return Err(e),
185+
Err(e) => {
186+
return Err(e);
187+
}
172188
}
173189
}
174190

175-
Ok(Node::Django(DjangoNode::Tag {
176-
kind,
177-
bits,
178-
children,
179-
}))
191+
Err(ParserError::StreamError(Stream::UnexpectedEof))
180192
}
181193

182194
fn parse_django_variable(&mut self, s: &str) -> Result<Node, ParserError> {
@@ -460,43 +472,34 @@ impl Parser {
460472
}
461473

462474
fn synchronize(&mut self) -> Result<(), ParserError> {
475+
println!("--- Starting synchronization ---");
463476
const SYNC_TYPES: &[TokenType] = &[
464477
TokenType::DjangoBlock(String::new()),
465478
TokenType::HtmlTagOpen(String::new()),
466-
TokenType::HtmlTagClose(String::new()), // Added
467479
TokenType::HtmlTagVoid(String::new()),
468480
TokenType::ScriptTagOpen(String::new()),
469-
TokenType::ScriptTagClose(String::new()), // Added
470481
TokenType::StyleTagOpen(String::new()),
471-
TokenType::StyleTagClose(String::new()), // Added
472482
TokenType::Newline,
473483
TokenType::Eof,
474484
];
475485

476-
let mut nesting = 0;
477486
while !self.is_at_end() {
478-
let token = self.peek()?;
479-
match token.token_type() {
480-
TokenType::HtmlTagOpen(_)
481-
| TokenType::ScriptTagOpen(_)
482-
| TokenType::StyleTagOpen(_) => {
483-
nesting += 1;
484-
}
485-
TokenType::HtmlTagClose(_)
486-
| TokenType::ScriptTagClose(_)
487-
| TokenType::StyleTagClose(_) => {
488-
nesting -= 1;
489-
if nesting < 0 {
490-
return Ok(());
491-
}
492-
}
493-
_ if SYNC_TYPES.contains(token.token_type()) && nesting == 0 => {
487+
let current = self.peek()?;
488+
println!("--- Sync checking token: {:?}", current);
489+
490+
// Debug print for token type comparison
491+
for sync_type in SYNC_TYPES {
492+
println!("--- Comparing with sync type: {:?}", sync_type);
493+
if matches!(current.token_type(), sync_type) {
494+
println!("--- Found sync point at: {:?}", current);
494495
return Ok(());
495496
}
496-
_ => {}
497497
}
498+
499+
println!("--- Consuming token in sync: {:?}", current);
498500
self.consume()?;
499501
}
502+
println!("--- Reached end during synchronization");
500503
Ok(())
501504
}
502505
}

crates/djls-ast/src/snapshots/djls_ast__parser__tests__parse_django_block.snap

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,3 @@ nodes:
1111
- user.is_staff
1212
children:
1313
- Text: Admin
14-
- Django:
15-
Tag:
16-
kind: Else
17-
bits:
18-
- else
19-
children:
20-
- Text: User

0 commit comments

Comments
 (0)