Skip to main content

liberty_parser/
ast.rs

1//! Defines the types and parser for the Abstract Syntax Tree (AST) representation of a Liberty
2//! file.
3//!
4
5use std::{fmt, result};
6
7use crate::error::Error;
8use crate::liberty::Liberty;
9use crate::parser::parse_libs;
10
11use gpoint::GPoint;
12use itertools::Itertools;
13use nom::error::VerboseError;
14
15/// Result type for parsing
16pub type ParseResult<'a, T> = result::Result<T, Error<'a>>;
17
18/// Liberty file AST representation
19///
20/// Each liberty file can have one or more `library`s defined in it, which are represented as a
21/// [`GroupItem::Group`] variant.
22#[derive(Debug, Clone)]
23pub struct LibertyAst(pub Vec<GroupItem>);
24
25impl LibertyAst {
26    /// Create a new AST from a vector of `GroupItem`s
27    pub fn new(libs: Vec<GroupItem>) -> Self {
28        Self(libs)
29    }
30
31    /// Parse a Liberty file's string representation into the AST
32    pub fn from_string(input: &str) -> ParseResult<'_, Self> {
33        parse_libs::<VerboseError<&str>>(input)
34            .map_err(|e| Error::new(input, e))
35            .map(|(_, libs)| LibertyAst::new(libs))
36    }
37
38    /// Convert an AST into a [`Liberty`] struct
39    pub fn into_liberty(self) -> Liberty {
40        Liberty::from_ast(self)
41    }
42
43    /// Convert a [`Liberty`] struct into an AST
44    pub fn from_liberty(lib: Liberty) -> Self {
45        lib.to_ast()
46    }
47}
48
49impl fmt::Display for LibertyAst {
50    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51        write!(f, "{}", items_to_string(&self.0, 0))
52    }
53}
54
55impl From<Liberty> for LibertyAst {
56    fn from(liberty: Liberty) -> Self {
57        LibertyAst::from_liberty(liberty)
58    }
59}
60
61// Build a quote-aligned backslash-continuation: one space, backslash, newline, `align` spaces.
62fn continuation(align: usize) -> String {
63    format!(" \\\n{}", " ".repeat(align))
64}
65
66// Recursively convert a vector of [`GroupItem`]s into a single `String`
67fn items_to_string(items: &[GroupItem], level: usize) -> String {
68    let indent = "  ".repeat(level);
69    items
70        .iter()
71        .map(|item| match item {
72            // A '\n' inside a Value::String used as a simple-attr value is re-escaped as an
73            // aligned backslash-continuation so the quoted string round-trips byte-for-byte;
74            // the parser's inverse pop-at-most-one-space rule undoes this on re-parse.
75            GroupItem::SimpleAttr(name, Value::String(s)) if s.contains('\n') => format!(
76                "{}{} : \"{}\";",
77                indent,
78                name,
79                s.replace('\n', &continuation(indent.len() + name.len() + 4))
80            ),
81            GroupItem::SimpleAttr(name, value) => {
82                format!("{}{} : {};", indent, name, value)
83            }
84            GroupItem::ComplexAttr(name, values) => {
85                let multiline =
86                    values.len() > 1 && values.iter().all(|v| matches!(v, Value::FloatGroup(_)));
87                // A '\n' inside a Value::String used as a complex-attr value is emitted raw —
88                // parseable and round-trip stable, no producer exists, deliberately out of scope.
89                let sep = if multiline {
90                    format!(",{}", continuation(indent.len() + name.len() + 2))
91                } else {
92                    ", ".to_string()
93                };
94                format!(
95                    "{}{} ({});",
96                    indent,
97                    name,
98                    values.iter().map(|v| v.to_string()).join(&sep)
99                )
100            }
101            GroupItem::Comment(v) => format!("/*\n{}\n*/", v),
102            GroupItem::Group(type_, name, group_items) => format!(
103                "{}{} ({}) {{\n{}\n{}}}",
104                indent,
105                type_,
106                name,
107                items_to_string(group_items, level + 1),
108                indent
109            ),
110        })
111        .join("\n")
112}
113
114/// Intermediate representation
115#[derive(Debug, PartialEq, Clone)]
116pub enum GroupItem {
117    // type, name, values
118    Group(String, String, Vec<GroupItem>),
119    // name, value
120    SimpleAttr(String, Value),
121    ComplexAttr(String, Vec<Value>),
122    // contents
123    Comment(String),
124}
125
126impl GroupItem {
127    /// Convert [`Value::Float`] to `f64` or panic
128    pub fn group(&self) -> (String, String, Vec<GroupItem>) {
129        if let GroupItem::Group(type_, name, items) = self {
130            (String::from(type_), String::from(name), items.clone())
131        } else {
132            panic!("Not variant GroupItem::Group");
133        }
134    }
135}
136
137/// Liberty value type
138///
139/// A wide range of types are defined for the Liberty syntax. Because there is little to no way
140/// to parse enumerated types from the syntax alone, enumerated types are parsed as the
141/// [`Value::Expression`] variant.
142#[derive(Debug, PartialEq, Clone)]
143pub enum Value {
144    /// Boolean value, parsed from the keywords `true` and `false`
145    Bool(bool),
146    /// Floating point value.
147    ///
148    /// All numbers are parsed into `f64`. While the Liberty specification differentiates between
149    /// integers and floating point values on a per-field basis, all are parsed into an `f64`.
150    Float(f64),
151    /// Group of floating point values in quotation marks
152    ///
153    /// For example, this complex attribute
154    ///
155    /// ```text
156    /// values ( \
157    ///   "1.0, 2.0, 3.0", \
158    ///   "4.0, 5.0, 6.0" \
159    /// );
160    /// ```
161    ///
162    /// will be parsed into a `Vec<Value::FloatGroup>`.
163    FloatGroup(Vec<f64>),
164    /// String enclosed in quotation marks
165    String(String),
166    /// Expression
167    ///
168    /// Enumerated values, such as the `delay_model` simple attribute,  are parsed as a
169    /// [`Value::Expression`].
170    Expression(String),
171}
172
173impl fmt::Display for Value {
174    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175        match self {
176            Value::Expression(v) => v.fmt(f),
177            Value::String(v) => write!(f, "\"{}\"", v),
178            Value::Bool(v) => {
179                if *v {
180                    write!(f, "true")
181                } else {
182                    write!(f, "false")
183                }
184            }
185            Value::Float(v) => write!(f, "{}", GPoint(*v)),
186            Value::FloatGroup(v) => write!(f, "\"{}\"", v.iter().map(|x| GPoint(*x)).format(", ")),
187        }
188    }
189}
190
191impl Value {
192    /// Convert [`Value::Float`] to `f64` or panic
193    pub fn float(&self) -> f64 {
194        if let Value::Float(v) = self {
195            *v
196        } else {
197            panic!("Not a float")
198        }
199    }
200
201    /// Convert [`Value::String`] to `String` or panic
202    pub fn string(&self) -> String {
203        if let Value::String(v) = self {
204            v.clone()
205        } else {
206            panic!("Not a string")
207        }
208    }
209
210    /// Convert [`Value::Expression`] to `String` or panic
211    pub fn expr(&self) -> String {
212        if let Value::Expression(v) = self {
213            v.clone()
214        } else {
215            panic!("Not a string")
216        }
217    }
218
219    /// Convert [`Value::Bool`] to `bool` or panic
220    pub fn bool(&self) -> bool {
221        if let Value::Bool(v) = self {
222            *v
223        } else {
224            panic!("Not a bool")
225        }
226    }
227
228    /// Convert [`Value::FloatGroup`] to `Vec<f64>` or panic
229    pub fn float_group(&self) -> Vec<f64> {
230        if let Value::FloatGroup(v) = self {
231            v.clone()
232        } else {
233            panic!("Not a float group")
234        }
235    }
236}
237
238#[cfg(test)]
239mod test {
240    use super::{LibertyAst, Value};
241
242    macro_rules! parse_file {
243        ($fname:ident) => {{
244            let data = include_str!(concat!("../data/", stringify!($fname), ".lib"));
245            LibertyAst::from_string(data).unwrap()
246        }};
247    }
248
249    #[test]
250    fn test_files() {
251        parse_file!(small);
252        parse_file!(cells);
253        parse_file!(cells_timing);
254    }
255
256    #[test]
257    fn test_values() {
258        assert!(!Value::Bool(false).bool());
259        assert_eq!(Value::Float(-3.45).float(), -3.45f64);
260        assert_eq!(Value::Expression("A & B".to_string()).expr(), "A & B");
261        assert_eq!(
262            Value::FloatGroup(vec![1.2, 3.4]).float_group(),
263            vec![1.2, 3.4]
264        );
265        assert_eq!(Value::String("abc def".to_string()).string(), "abc def");
266    }
267}