Skip to main content

liberty_parser/
parser.rs

1use crate::ast::{GroupItem, Value};
2
3use gpoint::GPoint;
4
5use itertools::Itertools;
6use nom::{
7    branch::alt,
8    bytes::complete::{is_a, is_not, tag, take_until, take_while},
9    character::complete::{alpha1, char, line_ending, multispace0, one_of},
10    combinator::{all_consuming, cut, map, map_res, opt, peek, recognize},
11    error::{context, ParseError},
12    multi::{fold_many0, separated_list},
13    number::complete::double,
14    sequence::{delimited, preceded, terminated, tuple},
15    IResult,
16};
17
18fn underscore_tag<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
19    context(
20        "underscore_tag",
21        recognize(preceded(
22            alpha1,
23            take_while(|c: char| c.is_alphanumeric() || c.eq_ignore_ascii_case(&'_')),
24        )),
25    )(input)
26}
27
28fn quoted_floats<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Vec<f64>, E> {
29    context(
30        "quoted floats",
31        preceded(
32            char('\"'),
33            terminated(
34                separated_list(
35                    preceded(multispace0, char(',')),
36                    preceded(multispace0, double),
37                ),
38                char('\"'),
39            ),
40        ),
41    )(input)
42}
43
44fn expression<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
45    context("expression", move |input| {
46        recognize(separated_list(
47            // operator
48            preceded(multispace0, is_a("+-*/")),
49            // operand
50            preceded(
51                multispace0,
52                preceded(
53                    opt(is_a("-")),
54                    alt((
55                        // sub expression
56                        preceded(char('('), cut(terminated(expression, char(')')))),
57                        // identifier
58                        underscore_tag,
59                        // constant
60                        recognize(double),
61                    )),
62                ),
63            ),
64        ))(input)
65    })(input)
66}
67
68fn quoted_string<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
69    context(
70        "quoted string",
71        preceded(char('\"'), cut(terminated(is_not("\""), char('\"')))),
72    )(input)
73}
74
75/// Unescape in-quote backslash line continuations back to literal '\n'.
76///
77/// This is the exact inverse of the serialiser's ` \`+newline+alignment
78/// re-escape: for every `\` immediately followed by a line ending, the single
79/// space the serialiser inserts before the backslash is removed (at most one,
80/// so genuine content trailing spaces and content ending in `\` survive), a
81/// single '\n' is emitted (CRLF normalised to LF), and the following alignment
82/// indent (' ' and '\t') is dropped.
83///
84/// Round-trip guarantee: `parse(serialise(s)) == s` for every `s` whose content
85/// lines do not BEGIN with ' ' or '\t'. Leading content whitespace on a line is
86/// indistinguishable from alignment indent and is dropped — a documented
87/// limitation.
88fn unescape_line_continuations(s: &str) -> String {
89    if !s.contains('\\') {
90        return s.to_string();
91    }
92    let mut out = String::with_capacity(s.len());
93    let mut chars = s.chars().peekable();
94    while let Some(c) = chars.next() {
95        if c == '\\' {
96            // Look for a line ending immediately after the backslash: either
97            // "\n" or "\r\n".
98            let is_continuation = match chars.peek() {
99                Some('\n') => {
100                    chars.next();
101                    true
102                }
103                Some('\r') => {
104                    // Only a continuation if the '\r' is followed by '\n'.
105                    let mut lookahead = chars.clone();
106                    lookahead.next();
107                    if lookahead.peek() == Some(&'\n') {
108                        chars.next(); // consume '\r'
109                        chars.next(); // consume '\n'
110                        true
111                    } else {
112                        false
113                    }
114                }
115                _ => false,
116            };
117            if is_continuation {
118                // Pop at most one trailing space the serialiser inserted.
119                if out.ends_with(' ') {
120                    out.pop();
121                }
122                out.push('\n');
123                // Skip the alignment indent.
124                while matches!(chars.peek(), Some(' ') | Some('\t')) {
125                    chars.next();
126                }
127            } else {
128                // Backslash not followed by a line ending (including at end of
129                // input) is copied verbatim.
130                out.push('\\');
131            }
132        } else {
133            out.push(c);
134        }
135    }
136    out
137}
138
139fn boolean<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, bool, E> {
140    map_res(alpha1, |s: &str| s.parse::<bool>())(input)
141}
142
143fn simple_attr_value<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Value, E> {
144    context(
145        "simple attr value",
146        preceded(
147            multispace0,
148            alt((
149                map(quoted_floats, Value::FloatGroup),
150                map(quoted_string, |s| {
151                    Value::String(unescape_line_continuations(s))
152                }),
153                map(terminated(double, peek(one_of(",; \t)"))), Value::Float),
154                map(boolean, Value::Bool),
155                map(map(expression, String::from), Value::Expression),
156            )),
157        ),
158    )(input)
159}
160
161fn simple_attribute<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, GroupItem, E> {
162    context(
163        "simple attr",
164        map(
165            tuple((
166                preceded(multispace0, underscore_tag),
167                preceded(multispace0, char(':')),
168                cut(preceded(multispace0, simple_attr_value)),
169                preceded(multispace0, char(';')),
170            )),
171            |(name, _, value, _)| GroupItem::SimpleAttr(name.to_string(), value),
172        ),
173    )(input)
174}
175
176fn complex_attribute_values<'a, E: ParseError<&'a str>>(
177    input: &'a str,
178) -> IResult<&'a str, Vec<Value>, E> {
179    context(
180        "complex values",
181        delimited(
182            preceded(multispace0, tag("(")),
183            delimited(
184                opt(tuple((multispace0, tag("\\"), line_ending))),
185                separated_list(
186                    alt((
187                        map(
188                            tuple((multispace0, tag(","), multispace0, tag("\\"), line_ending)),
189                            |_| Some(1),
190                        ),
191                        map(tuple((multispace0, tag(","))), |_| Some(1)),
192                        map(
193                            tuple((multispace0, tag("\\"), line_ending, multispace0, tag(","))),
194                            |_| Some(1),
195                        ),
196                    )),
197                    preceded(multispace0, simple_attr_value),
198                ),
199                opt(tuple((multispace0, tag("\\"), line_ending))),
200            ),
201            preceded(multispace0, tag(")")),
202        ),
203    )(input)
204}
205
206fn complex_attribute<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, GroupItem, E> {
207    context(
208        "complex attr",
209        map(
210            tuple((
211                preceded(multispace0, underscore_tag),
212                preceded(multispace0, complex_attribute_values),
213                preceded(multispace0, char(';')),
214            )),
215            |(name, value, _)| GroupItem::ComplexAttr(name.to_string(), value),
216        ),
217    )(input)
218}
219
220fn comment<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
221    context(
222        "comment",
223        recognize(delimited(tag("/*"), take_until("*/"), tag("*/"))),
224    )(input)
225}
226
227fn parse_group_body<'a, E: ParseError<&'a str>>(
228    input: &'a str,
229) -> IResult<&'a str, Vec<GroupItem>, E> {
230    context(
231        "group body",
232        fold_many0(
233            context(
234                "folding items",
235                alt((
236                    map(
237                        map(preceded(multispace0, comment), String::from),
238                        GroupItem::Comment,
239                    ),
240                    preceded(multispace0, parse_group),
241                    preceded(multispace0, simple_attribute),
242                    preceded(multispace0, complex_attribute),
243                )),
244            ),
245            Vec::new(),
246            |mut acc: Vec<_>, item| {
247                acc.push(item);
248                acc
249            },
250        ),
251    )(input)
252}
253fn parse_group<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, GroupItem, E> {
254    context(
255        "parsing group",
256        map(
257            tuple((
258                preceded(multispace0, underscore_tag),
259                preceded(
260                    preceded(multispace0, char('(')),
261                    terminated(
262                        map(
263                            separated_list(
264                                preceded(multispace0, char(',')),
265                                preceded(
266                                    multispace0,
267                                    alt((
268                                        map(quoted_string, |s| format!("\"{}\"", s)),
269                                        map(underscore_tag, |s| s.to_string()),
270                                        map(double, |s| format!("{}", GPoint(s))),
271                                        map(quoted_floats, |s| {
272                                            format!(
273                                                "\"{}\"",
274                                                s.into_iter()
275                                                    .map(|f| format!("{}", GPoint(f)))
276                                                    .format(",")
277                                            )
278                                        }),
279                                    )),
280                                ),
281                            ),
282                            |vals: Vec<String>| vals.join(", "),
283                        ),
284                        preceded(multispace0, char(')')),
285                    ),
286                ),
287                preceded(
288                    preceded(multispace0, char('{')),
289                    cut(terminated(
290                        parse_group_body,
291                        preceded(multispace0, char('}')),
292                    )),
293                ),
294            )),
295            |(gtype, name, body)| GroupItem::Group(gtype.to_string(), name, body),
296        ),
297    )(input)
298}
299
300pub fn parse_libs<'a, E: ParseError<&'a str>>(
301    input: &'a str,
302) -> IResult<&'a str, Vec<GroupItem>, E> {
303    context(
304        "parse_libs",
305        all_consuming(terminated(
306            fold_many0(
307                alt((
308                    context(
309                        "outer comment",
310                        map(
311                            map(delimited(multispace0, comment, multispace0), String::from),
312                            GroupItem::Comment,
313                        ),
314                    ),
315                    preceded(multispace0, context("parse_lib", parse_group)),
316                )),
317                Vec::new(),
318                |mut acc: Vec<_>, item| {
319                    match &item {
320                        GroupItem::Group(_, _, _) => acc.push(item),
321                        GroupItem::Comment(_) => {}
322                        GroupItem::SimpleAttr(_, _) => {}
323                        GroupItem::ComplexAttr(_, _) => {}
324                    }
325                    acc
326                },
327            ),
328            multispace0,
329        )),
330    )(input)
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use nom::{
337        error::{convert_error, ErrorKind, VerboseError},
338        Err,
339    };
340
341    #[test]
342    fn test_unescape_no_backslash_identity() {
343        assert_eq!(unescape_line_continuations("A , B , C"), "A , B , C");
344        assert_eq!(unescape_line_continuations(""), "");
345    }
346
347    #[test]
348    fn test_unescape_continuation_drops_indent() {
349        // "A ,"+space+backslash+newline+alignment -> "A ,\nB"
350        // The single space before the backslash is serialiser-inserted and is
351        // popped; the alignment indent before B is dropped.
352        assert_eq!(
353            unescape_line_continuations("A , \\\n            B"),
354            "A ,\nB"
355        );
356    }
357
358    #[test]
359    fn test_unescape_trailing_space_pops_at_most_one() {
360        // Two spaces before the backslash: one is content, one is the
361        // serialiser-inserted space that gets popped.
362        assert_eq!(unescape_line_continuations("A ,  \\\n"), "A , \n");
363    }
364
365    #[test]
366    fn test_unescape_crlf_normalised() {
367        assert_eq!(
368            unescape_line_continuations("A , \\\r\n            B"),
369            "A ,\nB"
370        );
371    }
372
373    #[test]
374    fn test_unescape_literal_backslash_mid_string_preserved() {
375        assert_eq!(unescape_line_continuations("a\\b"), "a\\b");
376        // Bare CR (not part of CRLF) after backslash is not a continuation.
377        assert_eq!(unescape_line_continuations("a\\\rb"), "a\\\rb");
378    }
379
380    #[test]
381    fn test_unescape_backslash_at_end_preserved() {
382        assert_eq!(unescape_line_continuations("abc\\"), "abc\\");
383    }
384
385    #[test]
386    fn test_complex_attr_values() {
387        assert_eq!(
388            complex_attribute_values::<VerboseError<&str>>(
389                r#"( \
390    "0, 0.18, 0.33", \
391    "-0.555, -0.45, -0.225" \
392    )"#
393            ),
394            Ok((
395                "",
396                vec![
397                    Value::FloatGroup(vec![0.0, 0.18, 0.33]),
398                    Value::FloatGroup(vec![-0.555, -0.45, -0.225]),
399                ]
400            ))
401        );
402        assert_eq!(
403            complex_attribute_values::<VerboseError<&str>>(
404                r#"( \
405                 "a string(b)" \
406                 )"#
407            ),
408            Ok(("", vec![Value::String("a string(b)".to_string())]))
409        );
410        assert_eq!(
411            complex_attribute_values::<VerboseError<&str>>("(123,-456)"),
412            Ok(("", vec![Value::Float(123.0), Value::Float(-456.0),]))
413        );
414    }
415
416    #[test]
417    fn test_complex_attr() {
418        assert_eq!(
419            complex_attribute::<(&str, ErrorKind)>("capacitive_load_unit (1,pf);"),
420            Ok((
421                "",
422                GroupItem::ComplexAttr(
423                    "capacitive_load_unit".to_string(),
424                    vec![Value::Float(1.0), Value::Expression("pf".to_string()),],
425                )
426            ))
427        );
428    }
429
430    #[test]
431    fn test_complex_attr_multi_line() {
432        assert_eq!(
433            complex_attribute::<(&str, ErrorKind)>(
434                r#"values ( \
435    "0, 0.18, 0.33", \
436    "-0.555, -0.45, -0.225");"#
437            ),
438            Ok((
439                "",
440                GroupItem::ComplexAttr(
441                    "values".to_string(),
442                    vec![
443                        Value::FloatGroup(vec![0.0, 0.18, 0.33]),
444                        Value::FloatGroup(vec![-0.555, -0.45, -0.225]),
445                    ],
446                )
447            ))
448        );
449    }
450
451    #[test]
452    fn test_comment() {
453        assert_eq!(
454            comment::<(&str, ErrorKind)>("/*** abc **/def"),
455            Ok(("def", "/*** abc **/"))
456        );
457        assert_eq!(
458            comment::<(&str, ErrorKind)>(
459                "/* multi
460line
461**
462**/
463**/rest"
464            ),
465            Ok((
466                "
467**/rest",
468                "/* multi
469line
470**
471**/"
472            ))
473        );
474    }
475
476    #[test]
477    fn test_underscore_tag() {
478        assert_eq!(
479            underscore_tag::<(&str, ErrorKind)>("a_b__c"),
480            Ok(("", "a_b__c"))
481        );
482        assert_eq!(
483            underscore_tag::<(&str, ErrorKind)>("abc other"),
484            Ok((" other", "abc"))
485        );
486        assert_eq!(
487            underscore_tag::<(&str, ErrorKind)>("nand2"),
488            Ok(("", "nand2"))
489        );
490        assert_eq!(
491            underscore_tag::<(&str, ErrorKind)>("_"),
492            Err(Err::Error(("_", ErrorKind::Alpha)))
493        );
494        assert_eq!(
495            underscore_tag::<(&str, ErrorKind)>(" a_b"),
496            Err(Err::Error((" a_b", ErrorKind::Alpha)))
497        );
498        assert_eq!(
499            underscore_tag::<(&str, ErrorKind)>(",,"),
500            Err(Err::Error((",,", ErrorKind::Alpha)))
501        );
502    }
503
504    #[test]
505    fn test_simple_attribute_malformed() {
506        assert_eq!(
507            simple_attribute::<(&str, ErrorKind)>("attr_name : a b ; "),
508            Err(Err::Error(("b ; ", ErrorKind::Char))),
509        );
510    }
511    #[test]
512    fn test_simple_attribute_bool() {
513        assert_eq!(
514            simple_attribute::<(&str, ErrorKind)>("attr_name : true ; "),
515            Ok((
516                " ",
517                GroupItem::SimpleAttr(String::from("attr_name"), Value::Bool(true),)
518            ))
519        );
520        assert_eq!(
521            simple_attribute::<(&str, ErrorKind)>("attr_name : false ; "),
522            Ok((
523                " ",
524                GroupItem::SimpleAttr(String::from("attr_name"), Value::Bool(false),)
525            ))
526        );
527    }
528    #[test]
529    fn test_simple_attribute_float() {
530        assert_eq!(
531            simple_attribute::<(&str, ErrorKind)>("attr_name : 345.123 ; "),
532            Ok((
533                " ",
534                GroupItem::SimpleAttr(String::from("attr_name"), Value::Float(345.123),)
535            ))
536        );
537        assert_eq!(
538            simple_attribute::<(&str, ErrorKind)>("attr_name : -345.123 ; "),
539            Ok((
540                " ",
541                GroupItem::SimpleAttr(String::from("attr_name"), Value::Float(-345.123),)
542            ))
543        );
544    }
545    #[test]
546    fn test_simple_attribute_int() {
547        assert_eq!(
548            simple_attribute::<(&str, ErrorKind)>("attr_name : 345 ; "),
549            Ok((
550                " ",
551                GroupItem::SimpleAttr(String::from("attr_name"), Value::Float(345.0),)
552            ))
553        );
554        assert_eq!(
555            simple_attribute::<(&str, ErrorKind)>("attr_name : -345 ; "),
556            Ok((
557                " ",
558                GroupItem::SimpleAttr(String::from("attr_name"), Value::Float(-345.0),)
559            ))
560        );
561    }
562
563    #[test]
564    fn test_expression() {
565        let expressions = vec![
566            "A",
567            "-A",
568            "--A",
569            "A + B",
570            "A - B",
571            "A * B",
572            "A / B",
573            "(A + B)",
574            "((A + B))",
575            "(A / B) + C",
576            "(A / B) - (C * D)",
577            /****** with constants ******/
578            "A + 0.123",
579            "A - .123",
580            "A * 0",
581            "A / 1",
582            "(A + 0.5)",
583            "((1.23 + B))",
584            "(4.5 / B) + C",
585            "(7.8 / B) - (C * D)",
586        ];
587        for expr in expressions {
588            assert_eq!(expression::<(&str, ErrorKind)>(expr), Ok(("", expr)));
589        }
590    }
591
592    #[test]
593    fn test_simple_attribute_expression() {
594        assert_eq!(
595            simple_attribute::<(&str, ErrorKind)>("attr_name : nand2; "),
596            Ok((
597                " ",
598                GroupItem::SimpleAttr(
599                    String::from("attr_name"),
600                    Value::Expression(String::from("nand2")),
601                )
602            ))
603        );
604        assert_eq!(
605            simple_attribute::<(&str, ErrorKind)>("attr_name : table_lookup; "),
606            Ok((
607                " ",
608                GroupItem::SimpleAttr(
609                    String::from("attr_name"),
610                    Value::Expression(String::from("table_lookup")),
611                )
612            ))
613        );
614        let data = "attr_name : A +B; ";
615        match simple_attribute::<VerboseError<&str>>(data) {
616            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
617                println!("Error: {}", convert_error(data, err));
618                assert_eq!(true, false);
619            }
620            _ => {}
621        }
622        assert_eq!(
623            simple_attribute::<(&str, ErrorKind)>("attr_name : A + 1.2; "),
624            Ok((
625                " ",
626                GroupItem::SimpleAttr(
627                    String::from("attr_name"),
628                    Value::Expression(String::from("A + 1.2")),
629                )
630            ))
631        );
632    }
633
634    #[test]
635    fn test_simple_attribute_string() {
636        assert_eq!(
637            simple_attribute::<(&str, ErrorKind)>("attr_name : \"table_lookup\"; "),
638            Ok((
639                " ",
640                GroupItem::SimpleAttr(
641                    String::from("attr_name"),
642                    Value::String(String::from("table_lookup"))
643                )
644            ))
645        );
646    }
647
648    #[test]
649    fn test_parse_group() {
650        let data = "library ( foo ) {
651            abc ( 1, 2, 3 );
652        }";
653        match parse_group::<VerboseError<&str>>(data) {
654            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
655                println!("Error: {}", convert_error(data, err));
656                assert_eq!(true, false);
657            }
658            _ => {}
659        };
660        assert_eq!(
661            parse_group::<(&str, ErrorKind)>(data),
662            Ok((
663                "",
664                GroupItem::Group(
665                    "library".to_string(),
666                    "foo".to_string(),
667                    vec![GroupItem::ComplexAttr(
668                        "abc".to_string(),
669                        vec![Value::Float(1.0), Value::Float(2.0), Value::Float(3.0),],
670                    ),],
671                ),
672            ))
673        );
674    }
675
676    #[test]
677    fn test_nested_group() {
678        assert_eq!(
679            parse_group::<(&str, ErrorKind)>(
680                r#"
681            outer( outer ) {
682                inner ( inner) {
683                    abc ( 1, 2, 3 );
684                }
685                inner(inner2 ) {
686                    abc ( 1, 2, 3 );
687                }
688            }"#
689            ),
690            Ok((
691                "",
692                GroupItem::Group(
693                    "outer".to_string(),
694                    "outer".to_string(),
695                    vec![
696                        GroupItem::Group(
697                            "inner".to_string(),
698                            "inner".to_string(),
699                            vec![GroupItem::ComplexAttr(
700                                "abc".to_string(),
701                                vec![Value::Float(1.0), Value::Float(2.0), Value::Float(3.0),],
702                            ),],
703                        ),
704                        GroupItem::Group(
705                            "inner".to_string(),
706                            "inner2".to_string(),
707                            vec![GroupItem::ComplexAttr(
708                                "abc".to_string(),
709                                vec![Value::Float(1.0), Value::Float(2.0), Value::Float(3.0),],
710                            ),],
711                        ),
712                    ]
713                )
714            ))
715        );
716    }
717
718    #[test]
719    fn test_lib_simple() {
720        assert_eq!(
721            parse_libs::<(&str, ErrorKind)>(
722                r#"
723/*
724 delay model :       typ
725 check model :       typ
726 power model :       typ
727 capacitance model : typ
728 other model :       typ
729*/
730library(foo) {
731
732  delay_model : table_lookup;
733  /* unit attributes */
734  time_unit : "1ns";
735  capacitive_load_unit (1, pf );
736  function: "A & B";
737
738  slew_upper_threshold_pct_rise : 80;
739  nom_temperature : 25.0;
740}
741"#
742            ),
743            Ok((
744                "",
745                vec![GroupItem::Group(
746                    "library".to_string(),
747                    "foo".to_string(),
748                    vec![
749                        GroupItem::SimpleAttr(
750                            "delay_model".to_string(),
751                            Value::Expression("table_lookup".to_string())
752                        ),
753                        GroupItem::Comment("/* unit attributes */".to_string()),
754                        GroupItem::SimpleAttr(
755                            "time_unit".to_string(),
756                            Value::String("1ns".to_string())
757                        ),
758                        GroupItem::ComplexAttr(
759                            "capacitive_load_unit".to_string(),
760                            vec![Value::Float(1.0), Value::Expression("pf".to_string()),],
761                        ),
762                        GroupItem::SimpleAttr(
763                            "function".to_string(),
764                            Value::String("A & B".to_string()),
765                        ),
766                        GroupItem::SimpleAttr(
767                            "slew_upper_threshold_pct_rise".to_string(),
768                            Value::Float(80.0)
769                        ),
770                        GroupItem::SimpleAttr("nom_temperature".to_string(), Value::Float(25.0)),
771                    ],
772                ),]
773            ))
774        );
775    }
776}