Skip to content

Commit ce5fc11

Browse files
wip
1 parent 4232e40 commit ce5fc11

File tree

40 files changed

+1393
-484
lines changed

40 files changed

+1393
-484
lines changed

crates/djls-source/src/position.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ impl Span {
3232
Self { start, length }
3333
}
3434

35+
/// Construct a span from integer bounds expressed as byte offsets.
36+
#[must_use]
37+
pub fn from_bounds(start: usize, end: usize) -> Self {
38+
let start_u32 = u32::try_from(start).unwrap_or(u32::MAX);
39+
let end_u32 = u32::try_from(end).unwrap_or(u32::MAX);
40+
let length = end_u32.saturating_sub(start_u32);
41+
Self::new(start_u32, length)
42+
}
43+
3544
#[must_use]
3645
pub fn start_offset(&self) -> ByteOffset {
3746
ByteOffset(self.start)

crates/djls-templates/src/lexer.rs

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use djls_source::Span;
2+
13
use crate::db::Db as TemplateDb;
24
use crate::tokens::Token;
35
use crate::tokens::TokenContent;
6+
use crate::tokens::TokenSpans;
47

58
const BLOCK_TAG_START: &str = "{%";
69
const BLOCK_TAG_END: &str = "%}";
@@ -35,14 +38,14 @@ impl<'db> Lexer<'db> {
3538

3639
let token = match self.peek() {
3740
'{' => match self.peek_next() {
38-
'%' => self.lex_django_construct(BLOCK_TAG_END, |content, offset| {
39-
Token::Block { content, offset }
41+
'%' => self.lex_django_construct(BLOCK_TAG_END, |content, spans| {
42+
Token::Block { content, spans }
4043
}),
41-
'{' => self.lex_django_construct(VARIABLE_TAG_END, |content, offset| {
42-
Token::Variable { content, offset }
44+
'{' => self.lex_django_construct(VARIABLE_TAG_END, |content, spans| {
45+
Token::Variable { content, spans }
4346
}),
44-
'#' => self.lex_django_construct(COMMENT_TAG_END, |content, offset| {
45-
Token::Comment { content, offset }
47+
'#' => self.lex_django_construct(COMMENT_TAG_END, |content, spans| {
48+
Token::Comment { content, spans }
4649
}),
4750
_ => self.lex_text(),
4851
},
@@ -61,35 +64,45 @@ impl<'db> Lexer<'db> {
6164
fn lex_django_construct(
6265
&mut self,
6366
end: &str,
64-
token_fn: impl FnOnce(TokenContent<'db>, usize) -> Token<'db>,
67+
token_fn: impl FnOnce(TokenContent<'db>, TokenSpans) -> Token<'db>,
6568
) -> Token<'db> {
66-
let offset = self.start + 3;
69+
let opening_len = 2;
70+
let content_start = self.start + opening_len;
6771

6872
self.consume_n(2);
6973

7074
match self.consume_until(end) {
7175
Ok(text) => {
72-
self.consume_n(2);
7376
let content = TokenContent::new(self.db, text);
74-
token_fn(content, offset)
77+
let content_end = self.current;
78+
let span = Span::from_bounds(content_start, content_end);
79+
self.consume_n(end.len());
80+
let full_end = self.current;
81+
let full_span = Span::from_bounds(self.start, full_end);
82+
token_fn(content, TokenSpans::new(span, full_span))
7583
}
7684
Err(err_text) => {
77-
self.synchronize();
85+
let content_end = self.current;
86+
let span = Span::from_bounds(content_start, content_end);
87+
let full_span = Span::from_bounds(self.start, content_end);
7888
let content = TokenContent::new(self.db, err_text);
79-
Token::Error { content, offset }
89+
Token::Error {
90+
content,
91+
spans: TokenSpans::new(span, full_span),
92+
}
8093
}
8194
}
8295
}
8396

8497
fn lex_whitespace(&mut self, c: char) -> Token<'db> {
85-
let offset = self.start;
86-
8798
if c == '\n' || c == '\r' {
8899
self.consume(); // \r or \n
89100
if c == '\r' && self.peek() == '\n' {
90101
self.consume(); // \n of \r\n
91102
}
92-
Token::Newline { offset }
103+
let span = Span::from_bounds(self.start, self.current);
104+
let spans = TokenSpans::new(span, span);
105+
Token::Newline { spans }
93106
} else {
94107
self.consume(); // Consume the first whitespace
95108
while !self.is_at_end() && self.peek().is_whitespace() {
@@ -98,8 +111,9 @@ impl<'db> Lexer<'db> {
98111
}
99112
self.consume();
100113
}
101-
let count = self.current - self.start;
102-
Token::Whitespace { count, offset }
114+
let span = Span::from_bounds(self.start, self.current);
115+
let spans = TokenSpans::new(span, span);
116+
Token::Whitespace { spans }
103117
}
104118
}
105119

@@ -119,10 +133,9 @@ impl<'db> Lexer<'db> {
119133

120134
let text = &self.source[text_start..self.current];
121135
let content = TokenContent::new(self.db, text.to_string());
122-
Token::Text {
123-
content,
124-
offset: self.start,
125-
}
136+
let span = Span::from_bounds(self.start, self.current);
137+
let spans = TokenSpans::new(span, span);
138+
Token::Text { content, spans }
126139
}
127140

128141
#[inline]
@@ -156,26 +169,33 @@ impl<'db> Lexer<'db> {
156169

157170
fn consume_until(&mut self, delimiter: &str) -> Result<String, String> {
158171
let offset = self.current;
172+
let mut fallback: Option<usize> = None;
159173

160174
while self.current < self.source.len() {
161175
if self.source[self.current..].starts_with(delimiter) {
162-
return Ok(self.source[offset..self.current].trim().to_string());
176+
return Ok(self.source[offset..self.current].to_string());
163177
}
164-
self.consume();
165-
}
166178

167-
Err(self.source[offset..self.current].trim().to_string())
168-
}
169-
170-
fn synchronize(&mut self) {
171-
const SYNC_POINTS: &[u8] = b"{\n\r";
179+
if fallback.is_none()
180+
&& (self.source[self.current..].starts_with(BLOCK_TAG_START)
181+
|| self.source[self.current..].starts_with(VARIABLE_TAG_START)
182+
|| self.source[self.current..].starts_with(COMMENT_TAG_START))
183+
{
184+
fallback = Some(self.current);
185+
}
172186

173-
while !self.is_at_end() {
174-
if SYNC_POINTS.contains(&self.source.as_bytes()[self.current]) {
175-
return;
187+
let ch = self.peek();
188+
if fallback.is_none() && matches!(ch, '\n' | '\r') {
189+
fallback = Some(self.current);
176190
}
191+
177192
self.consume();
178193
}
194+
195+
let end = fallback.unwrap_or(self.current);
196+
let text = self.source[offset..end].to_string();
197+
self.current = end;
198+
Err(text)
179199
}
180200
}
181201

crates/djls-templates/src/nodelist.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,22 @@ pub enum Node<'db> {
1616
name: TagName<'db>,
1717
bits: Vec<TagBit<'db>>,
1818
span: Span,
19+
full_span: Span,
1920
},
2021
Comment {
2122
content: String,
2223
span: Span,
24+
full_span: Span,
2325
},
2426
Text {
2527
span: Span,
28+
full_span: Span,
2629
},
2730
Variable {
2831
var: VariableName<'db>,
2932
filters: Vec<FilterName<'db>>,
3033
span: Span,
34+
full_span: Span,
3135
},
3236
Error {
3337
node: ErrorNode,
@@ -41,25 +45,19 @@ impl<'db> Node<'db> {
4145
Node::Tag { span, .. }
4246
| Node::Variable { span, .. }
4347
| Node::Comment { span, .. }
44-
| Node::Text { span } => *span,
48+
| Node::Text { span, .. } => *span,
4549
Node::Error { node, .. } => node.span,
4650
}
4751
}
4852

4953
#[must_use]
5054
pub fn full_span(&self) -> Span {
5155
match self {
52-
// account for delimiters
53-
Node::Variable { span, .. }
54-
| Node::Comment { span, .. }
55-
| Node::Tag { span, .. } => {
56-
Span {
57-
start: span.start.saturating_sub(3),
58-
length: span.length + 6,
59-
}
60-
}
61-
Node::Error { node } => node.span,
62-
Node::Text { span } => *span,
56+
Node::Variable { full_span, .. }
57+
| Node::Comment { full_span, .. }
58+
| Node::Tag { full_span, .. }
59+
| Node::Text { full_span, .. } => *full_span,
60+
Node::Error { node } => node.full_span,
6361
}
6462
}
6563

@@ -90,6 +88,7 @@ impl<'db> Node<'db> {
9088
pub struct ErrorNode {
9189
pub content: String,
9290
pub span: Span,
91+
pub full_span: Span,
9392
pub error: ParseError,
9493
}
9594

0 commit comments

Comments
 (0)