1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2024 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use crate::ast::{Expr, SourceInfo, TemplateElement};
19
use crate::parser::primitives::{literal, try_literal};
20
use crate::parser::{error, expr, ParseResult};
21
use crate::reader::{Pos, Reader};
22

            
23
pub struct EncodedString {
24
    pub source_info: SourceInfo,
25
    pub chars: Vec<(char, String, Pos)>,
26
}
27

            
28
63655
pub fn template(reader: &mut Reader) -> ParseResult<Expr> {
29
63655
    try_literal("{{", reader)?;
30
310
    let expression = expr::parse2(reader)?;
31
310
    literal("}}", reader)?;
32
310
    Ok(expression)
33
}
34

            
35
31455
pub fn templatize(encoded_string: EncodedString) -> ParseResult<Vec<TemplateElement>> {
36
    enum State {
37
        String,
38
        Template,
39
        FirstOpenBracket,
40
        FirstCloseBracket,
41
    }
42

            
43
31455
    let mut elements = vec![];
44
31455

            
45
31455
    let mut value = String::new();
46
31455
    let mut encoded = String::new();
47
31455
    let mut state = State::String;
48
31455
    let mut expression_start = None;
49

            
50
490155
    for (c, s, pos) in encoded_string.chars {
51
458700
        match state {
52
            State::String => {
53
451100
                if s.as_str() == "{" {
54
1935
                    state = State::FirstOpenBracket;
55
449165
                } else {
56
449165
                    value.push(c);
57
449165
                    encoded.push_str(&s.clone());
58
                }
59
            }
60

            
61
            State::FirstOpenBracket => {
62
1915
                if s.as_str() == "{" {
63
715
                    if !value.is_empty() {
64
70
                        elements.push(TemplateElement::String { value, encoded });
65
70
                        value = String::new();
66
70
                        encoded = String::new();
67
                    }
68
715
                    state = State::Template;
69
1200
                } else {
70
1200
                    value.push('{');
71
1200
                    encoded.push('{');
72
1200

            
73
1200
                    value.push(c);
74
1200
                    encoded.push_str(&s.clone());
75
1200
                    state = State::String;
76
                }
77
            }
78

            
79
            State::Template => {
80
4970
                if expression_start.is_none() {
81
715
                    expression_start = Some(pos);
82
                }
83
4970
                if s.as_str() == "}" {
84
715
                    state = State::FirstCloseBracket;
85
4255
                } else {
86
4255
                    value.push(c);
87
4255
                    encoded.push_str(&s.clone());
88
                }
89
            }
90

            
91
            State::FirstCloseBracket => {
92
715
                if s.as_str() == "}" {
93
715
                    let mut reader = Reader::with_pos(encoded.as_str(), expression_start.unwrap());
94
715
                    let expression = expr::parse2(&mut reader)?;
95
715
                    elements.push(TemplateElement::Expression(expression));
96
715
                    value = String::new();
97
715
                    encoded = String::new();
98
715
                    expression_start = None;
99
715
                    state = State::String;
100
                } else {
101
                    value.push('}');
102
                    value.push(c);
103
                    encoded.push('}');
104
                    encoded.push_str(&s.clone());
105
                }
106
            }
107
        }
108
    }
109

            
110
31455
    match state {
111
31435
        State::String => {}
112
20
        State::FirstOpenBracket => {
113
20
            value.push('{');
114
20
            encoded.push('{');
115
        }
116
        State::Template | State::FirstCloseBracket => {
117
            let kind = error::ParseErrorKind::Expecting {
118
                value: "}}".to_string(),
119
            };
120
            return Err(error::ParseError::new(
121
                encoded_string.source_info.end,
122
                false,
123
                kind,
124
            ));
125
        }
126
    }
127

            
128
31455
    if !value.is_empty() {
129
30610
        elements.push(TemplateElement::String { value, encoded });
130
    }
131
31455
    Ok(elements)
132
}
133

            
134
#[cfg(test)]
135
mod tests {
136
    use super::*;
137
    use crate::ast::{Expr, Variable, Whitespace};
138

            
139
    #[test]
140
    fn test_templatize_empty_string() {
141
        let encoded_string = EncodedString {
142
            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
143
            chars: vec![],
144
        };
145
        assert_eq!(templatize(encoded_string).unwrap(), vec![]);
146
    }
147

            
148
    #[test]
149
    fn test_templatize_hello_world() {
150
        // Hi\u0020{{name}}!
151
        let encoded_string = EncodedString {
152
            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 18)),
153
            chars: vec![
154
                ('H', "H".to_string(), Pos { line: 1, column: 1 }),
155
                ('i', "i".to_string(), Pos { line: 1, column: 2 }),
156
                (' ', "\\u0020".to_string(), Pos { line: 1, column: 3 }),
157
                ('{', "{".to_string(), Pos { line: 1, column: 9 }),
158
                (
159
                    '{',
160
                    "{".to_string(),
161
                    Pos {
162
                        line: 1,
163
                        column: 10,
164
                    },
165
                ),
166
                (
167
                    'n',
168
                    "n".to_string(),
169
                    Pos {
170
                        line: 1,
171
                        column: 11,
172
                    },
173
                ),
174
                (
175
                    'a',
176
                    "a".to_string(),
177
                    Pos {
178
                        line: 1,
179
                        column: 12,
180
                    },
181
                ),
182
                (
183
                    'm',
184
                    "m".to_string(),
185
                    Pos {
186
                        line: 1,
187
                        column: 13,
188
                    },
189
                ),
190
                (
191
                    'e',
192
                    "e".to_string(),
193
                    Pos {
194
                        line: 1,
195
                        column: 14,
196
                    },
197
                ),
198
                (
199
                    '}',
200
                    "}".to_string(),
201
                    Pos {
202
                        line: 1,
203
                        column: 15,
204
                    },
205
                ),
206
                (
207
                    '}',
208
                    "}".to_string(),
209
                    Pos {
210
                        line: 1,
211
                        column: 16,
212
                    },
213
                ),
214
                (
215
                    '!',
216
                    "!".to_string(),
217
                    Pos {
218
                        line: 1,
219
                        column: 17,
220
                    },
221
                ),
222
            ],
223
        };
224
        assert_eq!(
225
            templatize(encoded_string).unwrap(),
226
            vec![
227
                TemplateElement::String {
228
                    value: "Hi ".to_string(),
229
                    encoded: "Hi\\u0020".to_string(),
230
                },
231
                TemplateElement::Expression(Expr {
232
                    space0: Whitespace {
233
                        value: String::new(),
234
                        source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 11)),
235
                    },
236
                    variable: Variable {
237
                        name: "name".to_string(),
238
                        source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 15)),
239
                    },
240
                    space1: Whitespace {
241
                        value: String::new(),
242
                        source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
243
                    },
244
                }),
245
                TemplateElement::String {
246
                    value: "!".to_string(),
247
                    encoded: "!".to_string(),
248
                },
249
            ]
250
        );
251
    }
252

            
253
    #[test]
254
    fn test_templatize_expression_only() {
255
        // {{x}}!
256
        let encoded_string = EncodedString {
257
            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 7)),
258
            chars: vec![
259
                ('{', "{".to_string(), Pos { line: 1, column: 1 }),
260
                ('{', "{".to_string(), Pos { line: 1, column: 2 }),
261
                ('x', "x".to_string(), Pos { line: 1, column: 3 }),
262
                ('}', "}".to_string(), Pos { line: 1, column: 4 }),
263
                ('}', "}".to_string(), Pos { line: 1, column: 4 }),
264
            ],
265
        };
266
        assert_eq!(
267
            templatize(encoded_string).unwrap(),
268
            vec![TemplateElement::Expression(Expr {
269
                space0: Whitespace {
270
                    value: String::new(),
271
                    source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
272
                },
273
                variable: Variable {
274
                    name: "x".to_string(),
275
                    source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 4)),
276
                },
277
                space1: Whitespace {
278
                    value: String::new(),
279
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
280
                },
281
            }),]
282
        );
283
    }
284

            
285
    #[test]
286
    fn test_templatize_error() {
287
        // missing closing
288
        // {{x
289
        let encoded_string = EncodedString {
290
            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 4)),
291
            chars: vec![
292
                ('{', "{".to_string(), Pos { line: 1, column: 1 }),
293
                ('{', "{".to_string(), Pos { line: 1, column: 2 }),
294
                ('x', "x".to_string(), Pos { line: 1, column: 3 }),
295
            ],
296
        };
297
        let error = templatize(encoded_string).err().unwrap();
298
        assert_eq!(error.pos, Pos { line: 1, column: 4 });
299
        assert_eq!(
300
            error.kind,
301
            error::ParseErrorKind::Expecting {
302
                value: "}}".to_string()
303
            }
304
        );
305
        assert!(!error.recoverable);
306
    }
307

            
308
    #[test]
309
    fn test_escape_bracket() {
310
        // \{\{
311
        // This is a valid string "{{"
312
        let encoded_string = EncodedString {
313
            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 4)),
314
            chars: vec![
315
                ('{', "\\{".to_string(), Pos { line: 1, column: 1 }),
316
                ('{', "\\{".to_string(), Pos { line: 1, column: 2 }),
317
            ],
318
        };
319
        assert_eq!(
320
            templatize(encoded_string).unwrap(),
321
            vec![TemplateElement::String {
322
                value: "{{".to_string(),
323
                encoded: "\\{\\{".to_string(),
324
            },]
325
        );
326
    }
327
}