1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2025 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::parser::primitives::{hex_digit, literal, try_literal};
21
use crate::parser::{template, ParseError, ParseErrorKind, ParseResult};
22
use crate::reader::Reader;
23

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

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

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

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

            
154
892930
fn any_char(except: &[char], reader: &mut Reader) -> ParseResult<(char, String)> {
155
892930
    let start = reader.cursor();
156
892930
    match escape_char(reader) {
157
2315
        Ok(c) => Ok((c, reader.read_from(start.index))),
158
890615
        Err(e) => {
159
890615
            if e.recoverable {
160
890610
                reader.seek(start);
161
890610
                match reader.read() {
162
                    None => {
163
640
                        let kind = ParseErrorKind::Expecting {
164
640
                            value: "char".to_string(),
165
640
                        };
166
640
                        Err(ParseError::new(start.pos, true, kind))
167
                    }
168
889970
                    Some(c) => {
169
889970
                        if except.contains(&c)
170
865715
                            || ['\\', '\x08', '\n', '\x0c', '\r', '\t'].contains(&c)
171
                        {
172
43635
                            let kind = ParseErrorKind::Expecting {
173
43635
                                value: "char".to_string(),
174
43635
                            };
175
43635
                            Err(ParseError::new(start.pos, true, kind))
176
                        } else {
177
846335
                            Ok((c, reader.read_from(start.index)))
178
                        }
179
                    }
180
                }
181
            } else {
182
5
                Err(e)
183
            }
184
        }
185
    }
186
}
187

            
188
896140
pub fn escape_char(reader: &mut Reader) -> ParseResult<char> {
189
896140
    try_literal("\\", reader)?;
190
2420
    let start = reader.cursor();
191
2420
    match reader.read() {
192
40
        Some('#') => Ok('#'),
193
660
        Some('"') => Ok('"'),
194
        Some('`') => Ok('`'),
195
395
        Some('\\') => Ok('\\'),
196
        Some('/') => Ok('/'),
197
        Some('b') => Ok('\x08'),
198
1105
        Some('n') => Ok('\n'),
199
        Some('f') => Ok('\x0c'),
200
20
        Some('r') => Ok('\r'),
201
50
        Some('t') => Ok('\t'),
202
145
        Some('u') => unicode(reader),
203
5
        _ => Err(ParseError::new(
204
5
            start.pos,
205
5
            false,
206
5
            ParseErrorKind::EscapeChar,
207
5
        )),
208
    }
209
}
210

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

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

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

            
244
    use super::*;
245
    use crate::ast::{Expr, ExprKind, Placeholder, TemplateElement, Variable, Whitespace};
246
    use crate::reader::Pos;
247
    use crate::typing::ToSource;
248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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