Skip to content

Commit f96b3da

Browse files
create djls-ast crate and implement lexer and parser (#8)
1 parent 81199d1 commit f96b3da

20 files changed

+2255
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ resolver = "2"
44

55
[workspace.dependencies]
66
djls = { path = "crates/djls" }
7+
djls-ast = { path = "crates/djls-ast" }
78
djls-django = { path = "crates/djls-django" }
89
djls-python = { path = "crates/djls-python" }
910

crates/djls-ast/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "djls-ast"
3+
version = "0.0.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
serde = { workspace = true }
8+
9+
thiserror = "2.0.6"
10+
11+
[dev-dependencies]
12+
insta = { version = "1.41.1", features = ["yaml"] }
13+
14+
[profile.dev.package]
15+
insta.opt-level = 3
16+
similar.opt-level = 3

crates/djls-ast/src/ast.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use serde::Serialize;
2+
use std::collections::BTreeMap;
3+
use std::str::FromStr;
4+
use thiserror::Error;
5+
6+
#[derive(Clone, Debug, Default, Serialize)]
7+
pub struct Ast {
8+
nodes: Vec<Node>,
9+
}
10+
11+
impl Ast {
12+
pub fn nodes(&self) -> &Vec<Node> {
13+
&self.nodes
14+
}
15+
16+
pub fn add_node(&mut self, node: Node) {
17+
self.nodes.push(node);
18+
}
19+
20+
pub fn finalize(&mut self) -> Result<Ast, AstError> {
21+
if self.nodes.is_empty() {
22+
return Err(AstError::EmptyAst);
23+
}
24+
Ok(self.clone())
25+
}
26+
}
27+
28+
#[derive(Clone, Debug, Serialize)]
29+
pub enum Node {
30+
Django(DjangoNode),
31+
Html(HtmlNode),
32+
Script(ScriptNode),
33+
Style(StyleNode),
34+
Text(String),
35+
}
36+
37+
#[derive(Clone, Debug, Serialize)]
38+
pub enum DjangoNode {
39+
Comment(String),
40+
Tag {
41+
kind: DjangoTagKind,
42+
bits: Vec<String>,
43+
children: Vec<Node>,
44+
},
45+
Variable {
46+
bits: Vec<String>,
47+
filters: Vec<DjangoFilter>,
48+
},
49+
}
50+
51+
#[derive(Clone, Debug, Serialize)]
52+
pub enum DjangoTagKind {
53+
Autoescape,
54+
Block,
55+
Comment,
56+
CsrfToken,
57+
Cycle,
58+
Debug,
59+
Elif,
60+
Else,
61+
Empty,
62+
Extends,
63+
Filter,
64+
FirstOf,
65+
For,
66+
If,
67+
IfChanged,
68+
Include,
69+
Load,
70+
Lorem,
71+
Now,
72+
Other(String),
73+
Querystring, // 5.1
74+
Regroup,
75+
ResetCycle,
76+
Spaceless,
77+
TemplateTag,
78+
Url,
79+
Verbatim,
80+
WidthRatio,
81+
With,
82+
}
83+
84+
impl DjangoTagKind {
85+
const AUTOESCAPE: &'static str = "autoescape";
86+
const BLOCK: &'static str = "block";
87+
const COMMENT: &'static str = "comment";
88+
const CSRF_TOKEN: &'static str = "csrf_token";
89+
const CYCLE: &'static str = "cycle";
90+
const DEBUG: &'static str = "debug";
91+
const ELIF: &'static str = "elif";
92+
const ELSE: &'static str = "else";
93+
const EMPTY: &'static str = "empty";
94+
const EXTENDS: &'static str = "extends";
95+
const FILTER: &'static str = "filter";
96+
const FIRST_OF: &'static str = "firstof";
97+
const FOR: &'static str = "for";
98+
const IF: &'static str = "if";
99+
const IF_CHANGED: &'static str = "ifchanged";
100+
const INCLUDE: &'static str = "include";
101+
const LOAD: &'static str = "load";
102+
const LOREM: &'static str = "lorem";
103+
const NOW: &'static str = "now";
104+
const QUERYSTRING: &'static str = "querystring";
105+
const REGROUP: &'static str = "regroup";
106+
const RESET_CYCLE: &'static str = "resetcycle";
107+
const SPACELESS: &'static str = "spaceless";
108+
const TEMPLATE_TAG: &'static str = "templatetag";
109+
const URL: &'static str = "url";
110+
const VERBATIM: &'static str = "verbatim";
111+
const WIDTH_RATIO: &'static str = "widthratio";
112+
const WITH: &'static str = "with";
113+
}
114+
115+
impl FromStr for DjangoTagKind {
116+
type Err = AstError;
117+
118+
fn from_str(s: &str) -> Result<Self, Self::Err> {
119+
if s.is_empty() {
120+
return Err(AstError::EmptyTag);
121+
}
122+
123+
match s {
124+
Self::AUTOESCAPE => Ok(Self::Autoescape),
125+
Self::BLOCK => Ok(Self::Block),
126+
Self::COMMENT => Ok(Self::Comment),
127+
Self::CSRF_TOKEN => Ok(Self::CsrfToken),
128+
Self::CYCLE => Ok(Self::Cycle),
129+
Self::DEBUG => Ok(Self::Debug),
130+
Self::ELIF => Ok(Self::Elif),
131+
Self::ELSE => Ok(Self::Else),
132+
Self::EMPTY => Ok(Self::Empty),
133+
Self::EXTENDS => Ok(Self::Extends),
134+
Self::FILTER => Ok(Self::Filter),
135+
Self::FIRST_OF => Ok(Self::FirstOf),
136+
Self::FOR => Ok(Self::For),
137+
Self::IF => Ok(Self::If),
138+
Self::IF_CHANGED => Ok(Self::IfChanged),
139+
Self::INCLUDE => Ok(Self::Include),
140+
Self::LOAD => Ok(Self::Load),
141+
Self::LOREM => Ok(Self::Lorem),
142+
Self::NOW => Ok(Self::Now),
143+
Self::QUERYSTRING => Ok(Self::Querystring),
144+
Self::REGROUP => Ok(Self::Regroup),
145+
Self::RESET_CYCLE => Ok(Self::ResetCycle),
146+
Self::SPACELESS => Ok(Self::Spaceless),
147+
Self::TEMPLATE_TAG => Ok(Self::TemplateTag),
148+
Self::URL => Ok(Self::Url),
149+
Self::VERBATIM => Ok(Self::Verbatim),
150+
Self::WIDTH_RATIO => Ok(Self::WidthRatio),
151+
Self::WITH => Ok(Self::With),
152+
other => Ok(Self::Other(other.to_string())),
153+
}
154+
}
155+
}
156+
157+
#[derive(Clone, Debug, Serialize)]
158+
pub struct DjangoFilter {
159+
name: String,
160+
arguments: Vec<String>,
161+
}
162+
163+
impl DjangoFilter {
164+
pub fn new(name: String, arguments: Vec<String>) -> Self {
165+
Self { name, arguments }
166+
}
167+
}
168+
169+
#[derive(Clone, Debug, Serialize)]
170+
pub enum HtmlNode {
171+
Comment(String),
172+
Doctype(String),
173+
Element {
174+
tag_name: String,
175+
attributes: Attributes,
176+
children: Vec<Node>,
177+
},
178+
Void {
179+
tag_name: String,
180+
attributes: Attributes,
181+
},
182+
}
183+
184+
#[derive(Clone, Debug, Serialize)]
185+
pub enum ScriptNode {
186+
Comment {
187+
content: String,
188+
kind: ScriptCommentKind,
189+
},
190+
Element {
191+
attributes: Attributes,
192+
children: Vec<Node>,
193+
},
194+
}
195+
196+
#[derive(Clone, Debug, Serialize)]
197+
pub enum ScriptCommentKind {
198+
SingleLine, // //
199+
MultiLine, // /* */
200+
}
201+
202+
#[derive(Clone, Debug, Serialize)]
203+
pub enum StyleNode {
204+
Comment(String),
205+
Element {
206+
attributes: Attributes,
207+
children: Vec<Node>,
208+
},
209+
}
210+
211+
#[derive(Clone, Debug, Serialize)]
212+
pub enum AttributeValue {
213+
Value(String),
214+
Boolean,
215+
}
216+
217+
pub type Attributes = BTreeMap<String, AttributeValue>;
218+
219+
#[derive(Error, Debug)]
220+
pub enum AstError {
221+
#[error("error parsing django tag, recieved empty tag name")]
222+
EmptyTag,
223+
#[error("empty ast")]
224+
EmptyAst,
225+
}

0 commit comments

Comments
 (0)