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}