bracket/parser/
block.rs

1use std::ops::Range;
2
3use crate::{
4    error::{ErrorInfo, SyntaxError},
5    lexer::{self, Lexer, Token},
6    parser::{
7        ast::{Block, Element, Lines, Node, Slice, Text, TextBlock},
8        call::{self, CallParseContext},
9        ParseState,
10    },
11    SyntaxResult,
12};
13
14/// Consume consecutive tokens into a single span.
15pub(crate) fn until<'source>(
16    lexer: &mut Lexer<'source>,
17    state: &mut ParseState,
18    mut span: Range<usize>,
19    end: &dyn Fn(&Token) -> bool,
20) -> (Range<usize>, Option<Token>) {
21    let mut next_token: Option<Token> = None;
22    while let Some(t) = lexer.next() {
23        if t.is_newline() {
24            *state.line_mut() += 1;
25        }
26        if !end(&t) {
27            *state.byte_mut() = t.span().end - 1;
28            span.end = t.span().end;
29        } else {
30            next_token = Some(t);
31            break;
32        }
33    }
34    return (span, next_token);
35}
36
37/// Parse text until a test indicates the end of the block.
38pub(crate) fn text_until<'source>(
39    source: &'source str,
40    lexer: &mut Lexer<'source>,
41    state: &mut ParseState,
42    span: Range<usize>,
43    end: &dyn Fn(&Token) -> bool,
44    wrap: &dyn Fn(TextBlock<'source>) -> Node<'source>,
45) -> Option<(Node<'source>, Option<Token>)> {
46    let text = span.end..span.end;
47    let open = span;
48    let line_range = state.line_range();
49    let (span, next_token) = until(lexer, state, text, end);
50    if let Some(ref close) = next_token {
51        let mut text = Text::new(source, span, line_range);
52        text.lines_end(state.line());
53        let block = TextBlock::new(source, text, open, close.span().clone());
54        return Some((wrap(block), next_token));
55    }
56    None
57}
58
59/// Parse a raw block `{{{{raw}}}}{{{{/raw}}}}`.
60pub(crate) fn raw<'source>(
61    source: &'source str,
62    lexer: &mut Lexer<'source>,
63    state: &mut ParseState,
64    span: Range<usize>,
65) -> SyntaxResult<Node<'source>> {
66    let mut block = Block::new(source, span.clone(), true, state.line_range());
67
68    let call =
69        call::parse(source, lexer, state, span.clone(), CallParseContext::Raw)?;
70
71    if !call.is_closed() {
72        return Err(SyntaxError::RawBlockOpenNotTerminated(
73            ErrorInfo::from((source, state)).into(),
74        ));
75    }
76
77    // NOTE: must have an accurate end span before reading the Text chunk!
78    let end_span = call.close_span().clone().unwrap();
79
80    let open_name = call.target().as_str();
81
82    block.set_call(call);
83
84    let end = |t: &Token| match t {
85        Token::Block(lex, _) => match lex {
86            lexer::Block::EndRawBlock => true,
87            _ => false,
88        },
89        _ => false,
90    };
91
92    let wrap = |t: TextBlock<'source>| Node::Text(t.into());
93
94    let maybe_node = text_until(source, lexer, state, end_span, &end, &wrap);
95    if let Some((node, next_token)) = maybe_node {
96        let span = if let Some(token) = next_token {
97            match token {
98                Token::Block(lex, span) => match lex {
99                    lexer::Block::EndRawBlock => span,
100                    _ => {
101                        return Err(SyntaxError::TokenEndRawBlock(
102                            ErrorInfo::from((source, state)).into(),
103                        ))
104                    }
105                },
106                _ => {
107                    return Err(SyntaxError::RawBlockNotTerminated(
108                        ErrorInfo::from((source, state)).into(),
109                    ))
110                }
111            }
112        } else {
113            return Err(SyntaxError::RawBlockNotTerminated(
114                ErrorInfo::from((source, state)).into(),
115            ));
116        };
117
118        block.push(node);
119
120        let end_tag =
121            call::parse(source, lexer, state, span, CallParseContext::Raw)?;
122
123        if let Some(close_span) = end_tag.close_span() {
124            let exit_span = end_tag.open_span().start..close_span.end;
125            block.exit(exit_span);
126        } else {
127            return Err(SyntaxError::RawBlockNotTerminated(
128                ErrorInfo::from((source, state)).into(),
129            ));
130        }
131
132        let end_name = end_tag.target().as_str();
133
134        if open_name != end_name {
135            let notes = vec![format!("opening name is '{}'", open_name)];
136            return Err(SyntaxError::TagNameMismatch(
137                ErrorInfo::from((source, state, notes)).into(),
138            ));
139        }
140
141        block.lines_end(state.line());
142
143        Ok(Node::Block(block))
144    } else {
145        Err(SyntaxError::RawBlockNotTerminated(
146            ErrorInfo::from((source, state)).into(),
147        ))
148    }
149}
150
151/// Parse a raw comment `{{!-- comment --}}`.
152pub(crate) fn raw_comment<'source>(
153    source: &'source str,
154    lexer: &mut Lexer<'source>,
155    state: &mut ParseState,
156    span: Range<usize>,
157) -> SyntaxResult<Node<'source>> {
158    let end = |t: &Token| match t {
159        Token::RawComment(lex, _) => match lex {
160            lexer::RawComment::End => true,
161            _ => false,
162        },
163        _ => false,
164    };
165
166    let wrap = |t: TextBlock<'source>| Node::RawComment(t);
167    let maybe_node = text_until(source, lexer, state, span, &end, &wrap);
168    if let Some((node, _)) = maybe_node {
169        Ok(node)
170    } else {
171        Err(SyntaxError::RawCommentNotTerminated(
172            ErrorInfo::from((source, state)).into(),
173        ))
174    }
175}
176
177/// Parse an escaped statement `\{{escaped}}`.
178pub(crate) fn raw_statement<'source>(
179    source: &'source str,
180    lexer: &mut Lexer<'source>,
181    state: &mut ParseState,
182    span: Range<usize>,
183) -> SyntaxResult<Node<'source>> {
184    let end = |t: &Token| match t {
185        Token::RawStatement(lex, _) => match lex {
186            lexer::RawStatement::End => true,
187            _ => false,
188        },
189        _ => false,
190    };
191
192    let wrap = |t: TextBlock<'source>| Node::RawStatement(t);
193    let maybe_node = text_until(source, lexer, state, span, &end, &wrap);
194    if let Some((node, _)) = maybe_node {
195        Ok(node)
196    } else {
197        Err(SyntaxError::RawStatementNotTerminated(
198            ErrorInfo::from((source, state)).into(),
199        ))
200    }
201}
202
203/// Parse a comment block `{{! comment }}`.
204pub(crate) fn comment<'source>(
205    source: &'source str,
206    lexer: &mut Lexer<'source>,
207    state: &mut ParseState,
208    span: Range<usize>,
209) -> SyntaxResult<Node<'source>> {
210    let end = |t: &Token| match t {
211        Token::Comment(lex, _) => match lex {
212            lexer::Comment::End => true,
213            _ => false,
214        },
215        _ => false,
216    };
217
218    let wrap = |t: TextBlock<'source>| Node::Comment(t);
219    let maybe_node = text_until(source, lexer, state, span, &end, &wrap);
220    if let Some((node, _)) = maybe_node {
221        Ok(node)
222    } else {
223        Err(SyntaxError::CommentNotTerminated(
224            ErrorInfo::from((source, state)).into(),
225        ))
226    }
227}
228
229/// Open a scoped block `{{# block}}`.
230pub(crate) fn scope<'source>(
231    source: &'source str,
232    lexer: &mut Lexer<'source>,
233    state: &mut ParseState,
234    span: Range<usize>,
235) -> SyntaxResult<Block<'source>> {
236    let mut block = Block::new(source, span.clone(), false, state.line_range());
237    let call =
238        call::parse(source, lexer, state, span, CallParseContext::Block)?;
239    block.set_call(call);
240    Ok(block)
241}