ludtwig_parser/lib.rs
1#![warn(clippy::pedantic)]
2#![allow(clippy::module_name_repetitions)]
3#![allow(clippy::doc_markdown)]
4//! # Parser
5//! This is a handwritten top-down parser for the [twig templating language](https://twig.symfony.com/) combined with HTML.
6//! It's mostly developed together with the linter / formatter [ludtwig](https://github.com/MalteJanz/ludtwig).
7//!
8//! Parsing both Twig and HTML together into a single hierarchical syntax tree gives some benefits,
9//! valid syntax trees follow some desirable properties:
10//! - non-self-closing HTML tags always need a corresponding closing tag
11//! - opening and closing HTML tag must exist inside the same Twig block
12//! - Twig syntax is only allowed in specific places instead of everywhere
13//!
14//! which in turn make these templates and the HTML generated in the end less error-prone.
15//!
16//! # Syntax trees
17//! The parser constructs a syntax tree based on the library [rowan](https://github.com/rust-analyzer/rowan).
18//! A conceptual overview can be found here [Syntax in rust-analyzer](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/syntax.md).
19//! Basically the syntax tree consists of three layers:
20//! - GreenNodes
21//! - SyntaxNodes (aka RedNodes)
22//! - AstNodes (defined in this crate)
23//!
24//! # Examples
25//!
26//! ## Parsing into a syntax tree
27//! ```
28//! use ludtwig_parser::syntax::untyped::{debug_tree, SyntaxNode};
29//!
30//! let parse = ludtwig_parser::parse("{{ 42 }}");
31//!
32//! // parsing might result in errors
33//! assert!(parse.errors.is_empty());
34//!
35//! // in any case it will always produce a usable syntax tree
36//! // and provide a green node of the root.
37//!
38//! // for getting a SyntaxNode of the root you can use this shortcut
39//! // or construct the root by hand from the GreenNode (see SyntaxNode::new_root)
40//! let (tree_root, errors) = parse.split();
41//!
42//! // you can now iterate or search through the syntax tree
43//! // or debug print it with
44//! println!("{}", debug_tree(&tree_root));
45//! # assert_eq!(debug_tree(&tree_root), r##"ROOT@0..8
46//! # TWIG_VAR@0..8
47//! # TK_OPEN_CURLY_CURLY@0..2 "{{"
48//! # TWIG_EXPRESSION@2..5
49//! # TWIG_LITERAL_NUMBER@2..5
50//! # TK_WHITESPACE@2..3 " "
51//! # TK_NUMBER@3..5 "42"
52//! # TK_WHITESPACE@5..6 " "
53//! # TK_CLOSE_CURLY_CURLY@6..8 "}}""##);
54//! ```
55//!
56//! ## Iterate in Preorder
57//! ```
58//! # let parse = ludtwig_parser::parse("{{ 42 }}");
59//! # let (tree_root, errors) = parse.split();
60//!
61//! use ludtwig_parser::syntax::untyped::WalkEvent;
62//! use ludtwig_parser::syntax::typed::AstNode;
63//! use ludtwig_parser::syntax::typed::TwigVar;
64//!
65//! let mut preorder = tree_root.preorder();
66//! while let Some(walk_event) = preorder.next() {
67//! match walk_event {
68//! WalkEvent::Enter(syntax_node) => {
69//! if let Some(twig_var) = TwigVar::cast(syntax_node) {
70//! // do something with ast node here
71//! }
72//! }
73//! WalkEvent::Leave(syntax_node) => {}
74//! }
75//! }
76//! ```
77//!
78//! ## Utilities for retrieving a specific AstNode or Token
79//! As of now there might be missing utility method implementations on AstNode's.
80//! You can use these instead to retrieve any AstNode / Token you want (under a given AstNode).
81//! ```
82//! # let parse = ludtwig_parser::parse("{{ 42 }}");
83//! # let (tree_root, errors) = parse.split();
84//!
85//! use ludtwig_parser::syntax::typed::{AstNode, support, TwigVar};
86//! use ludtwig_parser::syntax::untyped::SyntaxToken;
87//! use ludtwig_parser::T;
88//!
89//! // finding a specific AstNode in a (sub) tree (first occurrence)
90//! // Note: only looks through the direct children of the given SyntaxNode
91//! let twig_var: TwigVar = support::child(&tree_root).unwrap();
92//!
93//! // you can freely get the underlaying SyntaxNode of an AstNode with
94//! let twig_var_syntax_node = twig_var.syntax();
95//!
96//! // finding a specific Token in a (sub) tree (first occurrence)
97//! // Note: only looks through the direct children of the given SyntaxNode
98//! let twig_var_opening_braces: SyntaxToken = support::token(twig_var_syntax_node, T!["{{"]).unwrap();
99//!
100//! // finding specific AstNode's in a (sub) tree (all occurrence)
101//! // returns an iterator
102//! // Note: only looks through the direct children of the given SyntaxNode
103//! let twig_vars = support::children::<TwigVar>(&tree_root);
104//! # assert_eq!(twig_vars.count(), 1);
105//! ```
106//!
107
108pub use parser::parse;
109pub use parser::Parse;
110pub use parser::ParseError;
111
112use crate::lexer::lex;
113
114mod grammar;
115mod lexer;
116mod parser;
117pub mod syntax;
118
119pub use grammar::TWIG_NAME_REGEX;
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::syntax::typed::AstNode;
125 use crate::syntax::typed::HtmlTag;
126 use crate::syntax::untyped::SyntaxNode;
127 use rowan::ast::support;
128
129 #[test]
130 fn it_should_not_panic_on_simple_parse_call() {
131 let _ = parse("asdf");
132 }
133
134 #[test]
135 fn it_should_not_panic_on_prev_sibling_call() {
136 let parse = parse("<div>a<hr/></div>");
137 let root = SyntaxNode::new_root(parse.green_node);
138 // println!("{}", debug_tree(&root));
139
140 let prev = root.prev_sibling();
141 // println!("prev sibling: {:?}", prev);
142 assert!(prev.is_none());
143
144 let child: HtmlTag = support::child(&root).unwrap();
145 let prev = child.syntax().prev_sibling();
146 // println!("{:?} prev sibling: {:?}", child, prev);
147 assert!(prev.is_none());
148
149 let child: HtmlTag = support::child(child.body().unwrap().syntax()).unwrap();
150 let prev = child.syntax().prev_sibling();
151 // println!("{:?} prev sibling: {:?}", child, prev);
152 assert!(prev.is_some());
153 }
154}