ludtwig_parser/
lib.rs

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