1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 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::{SourceInfo, Template};
19
use crate::combinator::one_or_more;
20
use crate::reader::Reader;
21

            
22
use super::error::{ParseError, ParseErrorKind};
23
use super::primitives::{hex_digit, literal, try_literal};
24
use super::{ParseResult, template};
25

            
26
/// Steps:
27
/// 1- parse String until end of stream, end of line
28
///    the string does not contain trailing space
29
/// 2- templatize
30
20252
pub fn unquoted_template(reader: &mut Reader) -> ParseResult<Template> {
31
20252
    let start = reader.cursor();
32
20252
    let mut chars = vec![];
33
20252
    let mut spaces = vec![];
34
20252
    let mut end = start;
35
    loop {
36
560948
        let pos = reader.cursor().pos;
37
560948
        match any_char(&['#'], reader) {
38
20252
            Err(e) => {
39
20252
                if e.recoverable {
40
20252
                    break;
41
                } else {
42
                    return Err(e);
43
                }
44
            }
45
540696
            Ok((c, s)) => {
46
540696
                if s == "\n" {
47
                    break;
48
                }
49
540696
                if s == " " {
50
1844
                    spaces.push((c, s, pos));
51
1844
                } else {
52
538852
                    if !spaces.is_empty() {
53
1200
                        chars.append(&mut spaces);
54
1200
                        spaces = vec![];
55
                    }
56
538852
                    chars.push((c, s, pos));
57
538852
                    end = reader.cursor();
58
                }
59
            }
60
        }
61
    }
62
20252
    reader.seek(end);
63
20252
    let encoded_string = template::EncodedString {
64
20252
        source_info: SourceInfo::new(start.pos, end.pos),
65
20252
        chars,
66
20252
    };
67
20252
    let elements = template::templatize(encoded_string)?;
68
20244
    let template = Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
69
20244
    Ok(template)
70
}
71

            
72
// TODO: should return an EncodedString
73
// (decoding escape sequence)
74
604
pub fn quoted_oneline_string(reader: &mut Reader) -> ParseResult<String> {
75
604
    literal("\"", reader)?;
76
8048
    let s = reader.read_while(|c| c != '"' && c != '\n');
77
604
    literal("\"", reader)?;
78
600
    Ok(s)
79
}
80

            
81
26704
pub fn quoted_template(reader: &mut Reader) -> ParseResult<Template> {
82
26704
    let start = reader.cursor();
83
26704
    let mut end = start;
84
26704
    try_literal("\"", reader)?;
85
25640
    let mut chars = vec![];
86
    loop {
87
382436
        let pos = reader.cursor().pos;
88
382436
        let save = reader.cursor();
89
382436
        match any_char(&['"'], reader) {
90
25640
            Err(e) => {
91
25640
                if e.recoverable {
92
25636
                    reader.seek(save);
93
25636
                    break;
94
                } else {
95
4
                    return Err(e);
96
                }
97
            }
98
356796
            Ok((c, s)) => {
99
356796
                chars.push((c, s, pos));
100
356796
                end = reader.cursor();
101
            }
102
        }
103
    }
104
25636
    literal("\"", reader)?;
105
25632
    let encoded_string = template::EncodedString {
106
25632
        source_info: SourceInfo::new(start.pos, end.pos),
107
25632
        chars,
108
25632
    };
109
25632
    let elements = template::templatize(encoded_string)?;
110
25632
    let template = Template::new(
111
25632
        Some('"'),
112
25632
        elements,
113
25632
        SourceInfo::new(start.pos, reader.cursor().pos),
114
    );
115
25632
    Ok(template)
116
}
117

            
118
26188
pub fn backtick_template(reader: &mut Reader) -> ParseResult<Template> {
119
26188
    let delimiter = Some('`');
120
26188
    let start = reader.cursor();
121
26188
    let mut end = start;
122
26188
    try_literal("`", reader)?;
123
1672
    let mut chars = vec![];
124
    loop {
125
38240
        let pos = reader.cursor().pos;
126
38240
        let save = reader.cursor();
127
38240
        match any_char(&['`', '\n'], reader) {
128
1672
            Err(e) => {
129
1672
                if e.recoverable {
130
1672
                    reader.seek(save);
131
1672
                    break;
132
                } else {
133
                    return Err(e);
134
                }
135
            }
136
36568
            Ok((c, s)) => {
137
36568
                chars.push((c, s, pos));
138
36568
                end = reader.cursor();
139
            }
140
        }
141
    }
142
1672
    literal("`", reader)?;
143
1672
    let encoded_string = template::EncodedString {
144
1672
        source_info: SourceInfo::new(start.pos, end.pos),
145
1672
        chars,
146
1672
    };
147
1672
    let elements = template::templatize(encoded_string)?;
148
1672
    let template = Template::new(
149
1672
        delimiter,
150
1672
        elements,
151
1672
        SourceInfo::new(start.pos, reader.cursor().pos),
152
    );
153
1672
    Ok(template)
154
}
155

            
156
981624
fn any_char(except: &[char], reader: &mut Reader) -> ParseResult<(char, String)> {
157
981624
    let start = reader.cursor();
158
981624
    match escape_char(reader) {
159
2216
        Ok(c) => Ok((c, reader.read_from(start.index))),
160
979408
        Err(e) => {
161
979408
            if e.recoverable {
162
979404
                reader.seek(start);
163
979404
                match reader.read() {
164
                    None => {
165
604
                        let kind = ParseErrorKind::Expecting {
166
604
                            value: "char".to_string(),
167
604
                        };
168
604
                        Err(ParseError::new(start.pos, true, kind))
169
                    }
170
978800
                    Some(c) => {
171
978800
                        if except.contains(&c)
172
951308
                            || ['\\', '\x08', '\n', '\x0c', '\r', '\t'].contains(&c)
173
                        {
174
46956
                            let kind = ParseErrorKind::Expecting {
175
46956
                                value: "char".to_string(),
176
46956
                            };
177
46956
                            Err(ParseError::new(start.pos, true, kind))
178
                        } else {
179
931844
                            Ok((c, reader.read_from(start.index)))
180
                        }
181
                    }
182
                }
183
            } else {
184
4
                Err(e)
185
            }
186
        }
187
    }
188
}
189

            
190
981624
pub fn escape_char(reader: &mut Reader) -> ParseResult<char> {
191
981624
    try_literal("\\", reader)?;
192
2220
    let start = reader.cursor();
193
2220
    match reader.read() {
194
36
        Some('#') => Ok('#'),
195
632
        Some('"') => Ok('"'),
196
        Some('`') => Ok('`'),
197
412
        Some('\\') => Ok('\\'),
198
        Some('/') => Ok('/'),
199
        Some('b') => Ok('\x08'),
200
1020
        Some('n') => Ok('\n'),
201
        Some('f') => Ok('\x0c'),
202
16
        Some('r') => Ok('\r'),
203
28
        Some('t') => Ok('\t'),
204
72
        Some('u') => unicode(reader),
205
4
        _ => Err(ParseError::new(
206
4
            start.pos,
207
4
            false,
208
4
            ParseErrorKind::EscapeChar,
209
4
        )),
210
    }
211
}
212

            
213
208
pub(crate) fn unicode(reader: &mut Reader) -> ParseResult<char> {
214
208
    literal("{", reader)?;
215
208
    let v = hex_value(reader)?;
216
208
    let c = match std::char::from_u32(v) {
217
        None => {
218
            return Err(ParseError::new(
219
                reader.cursor().pos,
220
                false,
221
                ParseErrorKind::Unicode,
222
            ));
223
        }
224
208
        Some(c) => c,
225
    };
226
208
    literal("}", reader)?;
227
208
    Ok(c)
228
}
229

            
230
208
fn hex_value(reader: &mut Reader) -> ParseResult<u32> {
231
208
    let mut digits = one_or_more(hex_digit, reader)?;
232
208
    let mut v = 0;
233
208
    let mut weight = 1;
234
208
    digits.reverse();
235
532
    for d in digits.iter() {
236
532
        v += weight * d;
237
532
        weight *= 16;
238
    }
239
208
    Ok(v)
240
}
241

            
242
#[cfg(test)]
243
mod tests {
244
    use std::time::SystemTime;
245

            
246
    use super::*;
247
    use crate::ast::{Expr, ExprKind, Placeholder, TemplateElement, Variable, Whitespace};
248
    use crate::reader::{CharPos, Pos};
249
    use crate::types::ToSource;
250

            
251
    #[test]
252
    fn test_unquoted_template_empty() {
253
        let mut reader = Reader::new("");
254
        assert_eq!(
255
            unquoted_template(&mut reader).unwrap(),
256
            Template::new(
257
                None,
258
                vec![],
259
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
260
            )
261
        );
262
        assert_eq!(reader.cursor().index, CharPos(0));
263
    }
264

            
265
    #[test]
266
    fn test_unquoted_template_with_hash() {
267
        let mut reader = Reader::new("a#");
268
        assert_eq!(
269
            unquoted_template(&mut reader).unwrap(),
270
            Template::new(
271
                None,
272
                vec![TemplateElement::String {
273
                    value: "a".to_string(),
274
                    source: "a".to_source(),
275
                }],
276
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 2))
277
            )
278
        );
279
        assert_eq!(reader.cursor().index, CharPos(1));
280
    }
281

            
282
    #[test]
283
    fn test_unquoted_template_with_encoded_hash() {
284
        let mut reader = Reader::new("a\\u{23}");
285
        assert_eq!(
286
            unquoted_template(&mut reader).unwrap(),
287
            Template::new(
288
                None,
289
                vec![TemplateElement::String {
290
                    value: "a#".to_string(),
291
                    source: "a\\u{23}".to_source(),
292
                }],
293
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 8))
294
            )
295
        );
296
        assert_eq!(reader.cursor().index, CharPos(7));
297
    }
298

            
299
    #[test]
300
    fn test_unquoted_template_with_quote() {
301
        let mut reader = Reader::new("\"hi\"");
302
        assert_eq!(
303
            unquoted_template(&mut reader).unwrap(),
304
            Template::new(
305
                None,
306
                vec![TemplateElement::String {
307
                    value: "\"hi\"".to_string(),
308
                    source: "\"hi\"".to_source(),
309
                }],
310
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 5))
311
            )
312
        );
313
        assert_eq!(reader.cursor().index, CharPos(4));
314
    }
315

            
316
    #[test]
317
    fn test_unquoted_template_hello_world() {
318
        let mut reader = Reader::new("hello\\u{20}{{name}}!");
319
        assert_eq!(
320
            unquoted_template(&mut reader).unwrap(),
321
            Template::new(
322
                None,
323
                vec![
324
                    TemplateElement::String {
325
                        value: "hello ".to_string(),
326
                        source: "hello\\u{20}".to_source(),
327
                    },
328
                    TemplateElement::Placeholder(Placeholder {
329
                        space0: Whitespace {
330
                            value: String::new(),
331
                            source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 14)),
332
                        },
333
                        expr: Expr {
334
                            kind: ExprKind::Variable(Variable {
335
                                name: "name".to_string(),
336
                                source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 18)),
337
                            }),
338
                            source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 18)),
339
                        },
340
                        space1: Whitespace {
341
                            value: String::new(),
342
                            source_info: SourceInfo::new(Pos::new(1, 18), Pos::new(1, 18)),
343
                        },
344
                    }),
345
                    TemplateElement::String {
346
                        value: "!".to_string(),
347
                        source: "!".to_source(),
348
                    },
349
                ],
350
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 21))
351
            )
352
        );
353
        assert_eq!(reader.cursor().index, CharPos(20));
354
    }
355

            
356
    #[test]
357
    fn test_quoted_template() {
358
        let mut reader = Reader::new("\"\"");
359
        assert_eq!(
360
            quoted_template(&mut reader).unwrap(),
361
            Template::new(
362
                Some('"'),
363
                vec![],
364
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 3)),
365
            )
366
        );
367
        assert_eq!(reader.cursor().index, CharPos(2));
368

            
369
        let mut reader = Reader::new("\"a#\"");
370
        assert_eq!(
371
            quoted_template(&mut reader).unwrap(),
372
            Template::new(
373
                Some('"'),
374
                vec![TemplateElement::String {
375
                    value: "a#".to_string(),
376
                    source: "a#".to_source(),
377
                }],
378
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 5))
379
            )
380
        );
381
        assert_eq!(reader.cursor().index, CharPos(4));
382

            
383
        let mut reader = Reader::new("\"{0}\"");
384
        assert_eq!(
385
            quoted_template(&mut reader).unwrap(),
386
            Template::new(
387
                Some('"'),
388
                vec![TemplateElement::String {
389
                    value: "{0}".to_string(),
390
                    source: "{0}".to_source(),
391
                }],
392
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6))
393
            )
394
        );
395
        assert_eq!(reader.cursor().index, CharPos(5));
396
    }
397

            
398
    #[test]
399
    fn test_quoted_template_with_quote() {
400
        // "\"hi\""
401
        let mut reader = Reader::new("\"\\\"hi\\\"\"");
402
        assert_eq!(
403
            quoted_template(&mut reader).unwrap(),
404
            Template::new(
405
                Some('"'),
406
                vec![TemplateElement::String {
407
                    value: "\"hi\"".to_string(),
408
                    source: "\\\"hi\\\"".to_source()
409
                }],
410
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 9))
411
            )
412
        );
413
        assert_eq!(reader.cursor().index, CharPos(8));
414
    }
415

            
416
    #[test]
417
    fn test_quoted_template_error_missing_closing_quote() {
418
        let mut reader = Reader::new("\"not found");
419
        let error = quoted_template(&mut reader).err().unwrap();
420
        assert_eq!(
421
            error.pos,
422
            Pos {
423
                line: 1,
424
                column: 11
425
            }
426
        );
427
        assert!(!error.recoverable);
428
    }
429

            
430
    #[test]
431
    fn test_quoted_string() {
432
        let mut reader = Reader::new("\"\"");
433
        assert_eq!(quoted_oneline_string(&mut reader).unwrap(), "");
434
        assert_eq!(reader.cursor().index, CharPos(2));
435

            
436
        let mut reader = Reader::new("\"Hello\"");
437
        assert_eq!(quoted_oneline_string(&mut reader).unwrap(), "Hello");
438
        assert_eq!(reader.cursor().index, CharPos(7));
439
    }
440

            
441
    #[test]
442
    fn test_backtick_template() {
443
        let mut reader = Reader::new("``");
444
        assert_eq!(
445
            backtick_template(&mut reader).unwrap(),
446
            Template::new(
447
                Some('`'),
448
                vec![],
449
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 3))
450
            )
451
        );
452
        assert_eq!(reader.cursor().index, CharPos(2));
453

            
454
        let mut reader = Reader::new("`foo#`");
455
        assert_eq!(
456
            backtick_template(&mut reader).unwrap(),
457
            Template::new(
458
                Some('`'),
459
                vec![TemplateElement::String {
460
                    value: "foo#".to_string(),
461
                    source: "foo#".to_source(),
462
                }],
463
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 7))
464
            )
465
        );
466
        assert_eq!(reader.cursor().index, CharPos(6));
467

            
468
        let mut reader = Reader::new("`{0}`");
469
        assert_eq!(
470
            backtick_template(&mut reader).unwrap(),
471
            Template::new(
472
                Some('`'),
473
                vec![TemplateElement::String {
474
                    value: "{0}".to_string(),
475
                    source: "{0}".to_source(),
476
                }],
477
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6))
478
            )
479
        );
480
        assert_eq!(reader.cursor().index, CharPos(5));
481
    }
482

            
483
    #[test]
484
    fn test_backtick_template_with_backtick() {
485
        // `\`hi\``
486
        let mut reader = Reader::new("`\\`hi\\``");
487
        assert_eq!(
488
            backtick_template(&mut reader).unwrap(),
489
            Template::new(
490
                Some('`'),
491
                vec![TemplateElement::String {
492
                    value: "`hi`".to_string(),
493
                    source: "\\`hi\\`".to_source()
494
                }],
495
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 9))
496
            )
497
        );
498
        assert_eq!(reader.cursor().index, CharPos(8));
499
    }
500

            
501
    #[test]
502
    fn test_backtick_template_error_missing_closing_backtick() {
503
        let mut reader = Reader::new("`not found");
504
        let error = backtick_template(&mut reader).err().unwrap();
505
        assert_eq!(
506
            error.pos,
507
            Pos {
508
                line: 1,
509
                column: 11
510
            }
511
        );
512
        assert!(!error.recoverable);
513
    }
514

            
515
    #[test]
516
    fn test_any_char() {
517
        let mut reader = Reader::new("a");
518
        assert_eq!(any_char(&[], &mut reader).unwrap(), ('a', "a".to_string()));
519
        assert_eq!(reader.cursor().index, CharPos(1));
520

            
521
        let mut reader = Reader::new(" ");
522
        assert_eq!(any_char(&[], &mut reader).unwrap(), (' ', " ".to_string()));
523
        assert_eq!(reader.cursor().index, CharPos(1));
524

            
525
        let mut reader = Reader::new("\\t");
526
        assert_eq!(
527
            any_char(&[], &mut reader).unwrap(),
528
            ('\t', "\\t".to_string())
529
        );
530
        assert_eq!(reader.cursor().index, CharPos(2));
531

            
532
        let mut reader = Reader::new("#");
533
        assert_eq!(any_char(&[], &mut reader).unwrap(), ('#', "#".to_string()));
534
        assert_eq!(reader.cursor().index, CharPos(1));
535
    }
536

            
537
    #[test]
538
    fn test_any_char_quote() {
539
        let mut reader = Reader::new("\\\"");
540
        assert_eq!(
541
            any_char(&[], &mut reader).unwrap(),
542
            ('"', "\\\"".to_string())
543
        );
544
        assert_eq!(reader.cursor().index, CharPos(2));
545
    }
546

            
547
    #[test]
548
    fn test_any_char_error() {
549
        let mut reader = Reader::new("");
550
        let error = any_char(&[], &mut reader).err().unwrap();
551
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
552
        assert!(error.recoverable);
553

            
554
        let mut reader = Reader::new("#");
555
        let error = any_char(&['#'], &mut reader).err().unwrap();
556
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
557
        assert!(error.recoverable);
558

            
559
        let mut reader = Reader::new("\t");
560
        let error = any_char(&[], &mut reader).err().unwrap();
561
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
562
        assert!(error.recoverable);
563
    }
564

            
565
    #[test]
566
    fn test_escape_char() {
567
        let mut reader = Reader::new("\\n");
568
        assert_eq!(escape_char(&mut reader).unwrap(), '\n');
569
        assert_eq!(reader.cursor().index, CharPos(2));
570

            
571
        let mut reader = Reader::new("\\u{0a}");
572
        assert_eq!(escape_char(&mut reader).unwrap(), '\n');
573
        assert_eq!(reader.cursor().index, CharPos(6));
574

            
575
        let mut reader = Reader::new("x");
576
        let error = escape_char(&mut reader).err().unwrap();
577
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
578
        assert_eq!(
579
            error.kind,
580
            ParseErrorKind::Expecting {
581
                value: "\\".to_string()
582
            }
583
        );
584
        assert!(error.recoverable);
585
        assert_eq!(reader.cursor().index, CharPos(0));
586
    }
587

            
588
    #[test]
589
    fn test_unicode() {
590
        let mut reader = Reader::new("{000a}");
591
        assert_eq!(unicode(&mut reader).unwrap(), '\n');
592
        assert_eq!(reader.cursor().index, CharPos(6));
593

            
594
        let mut reader = Reader::new("{E9}");
595
        assert_eq!(unicode(&mut reader).unwrap(), 'é');
596
        assert_eq!(reader.cursor().index, CharPos(4));
597
    }
598

            
599
    #[test]
600
    fn test_hex_value() {
601
        let mut reader = Reader::new("20x");
602
        assert_eq!(hex_value(&mut reader).unwrap(), 32);
603

            
604
        let mut reader = Reader::new("x");
605
        let error = hex_value(&mut reader).err().unwrap();
606
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
607
        assert_eq!(error.kind, ParseErrorKind::HexDigit);
608
        assert!(!error.recoverable);
609
    }
610

            
611
    #[test]
612
    fn test_quoted_template_benchmark() {
613
        // benchmark tests not in stable toolchain yet
614
        // Simply log duration for the time-being
615
        let mut reader = Reader::new(
616
            format!(
617
                "\"Hello World!\"{}",
618
                (0..10_000_000).map(|_| "X").collect::<String>()
619
            )
620
            .as_str(),
621
        );
622

            
623
        let now = SystemTime::now();
624
        assert!(quoted_template(&mut reader).is_ok());
625
        assert_eq!(reader.cursor().index, CharPos(14));
626
        eprintln!("duration= {}", now.elapsed().unwrap().as_nanos());
627
    }
628
}