calyx_opt/traversal/
construct.rs

1use super::Visitor;
2use calyx_ir as ir;
3use calyx_utils::{CalyxResult, OutputFile};
4use itertools::Itertools;
5use linked_hash_map::LinkedHashMap;
6
7#[derive(Clone)]
8/// The value returned from parsing an option.
9pub enum ParseVal {
10    /// A boolean option.
11    Bool(bool),
12    /// A number option.
13    Num(i64),
14    /// A string option.
15    String(String),
16    /// A list of values.
17    List(Vec<ParseVal>),
18    /// An output stream (stdout, stderr, file name)
19    OutStream(OutputFile),
20}
21
22impl ParseVal {
23    pub fn bool(&self) -> bool {
24        let ParseVal::Bool(b) = self else {
25            panic!("Expected bool, got {self}");
26        };
27        *b
28    }
29
30    pub fn num(&self) -> i64 {
31        let ParseVal::Num(n) = self else {
32            panic!("Expected number, got {self}");
33        };
34        *n
35    }
36
37    pub fn string(&self) -> String {
38        let ParseVal::String(s) = self else {
39            panic!("Expected String, got {self}");
40        };
41        s.clone()
42    }
43
44    pub fn pos_num(&self) -> Option<u64> {
45        let n = self.num();
46        if n < 0 { None } else { Some(n as u64) }
47    }
48
49    pub fn num_list(&self) -> Vec<i64> {
50        match self {
51            ParseVal::List(l) => {
52                l.iter().map(ParseVal::num).collect::<Vec<_>>()
53            }
54            _ => panic!("Expected list of numbers, got {self}"),
55        }
56    }
57
58    /// Parse a list that should have exactly N elements. If elements are missing, then add None
59    /// to the end of the list.
60    pub fn num_list_exact<const N: usize>(&self) -> [Option<i64>; N] {
61        let list = self.num_list();
62        let len = list.len();
63        if len > N {
64            panic!("Expected list of {N} numbers, got {len}");
65        }
66        list.into_iter()
67            .map(Some)
68            .chain(std::iter::repeat_n(None, N - len))
69            .collect::<Vec<_>>()
70            .try_into()
71            .unwrap()
72    }
73
74    /// Returns an output stream if it is not the null stream
75    pub fn not_null_outstream(&self) -> Option<OutputFile> {
76        match self {
77            ParseVal::OutStream(o) => {
78                if matches!(o, OutputFile::Null) {
79                    None
80                } else {
81                    Some(o.clone())
82                }
83            }
84            _ => panic!("Expected output stream, got {self}"),
85        }
86    }
87}
88impl std::fmt::Display for ParseVal {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            ParseVal::Bool(b) => write!(f, "{b}"),
92            ParseVal::Num(n) => write!(f, "{n}"),
93            ParseVal::String(s) => write!(f, "{s}"),
94            ParseVal::List(l) => {
95                write!(f, "[")?;
96                for (i, e) in l.iter().enumerate() {
97                    if i != 0 {
98                        write!(f, ", ")?;
99                    }
100                    write!(f, "{e}")?;
101                }
102                write!(f, "]")
103            }
104            ParseVal::OutStream(o) => write!(f, "{o}"),
105        }
106    }
107}
108
109/// Option that can be passed to a pass.
110pub struct PassOpt {
111    name: &'static str,
112    description: &'static str,
113    default: ParseVal,
114    parse: fn(&str) -> Option<ParseVal>,
115}
116
117impl PassOpt {
118    pub const fn new(
119        name: &'static str,
120        description: &'static str,
121        default: ParseVal,
122        parse: fn(&str) -> Option<ParseVal>,
123    ) -> Self {
124        Self {
125            name,
126            description,
127            default,
128            parse,
129        }
130    }
131
132    pub const fn name(&self) -> &'static str {
133        self.name
134    }
135
136    pub const fn description(&self) -> &'static str {
137        self.description
138    }
139
140    pub const fn default(&self) -> &ParseVal {
141        &self.default
142    }
143
144    fn parse(&self, s: &str) -> Option<ParseVal> {
145        (self.parse)(s)
146    }
147
148    /// Parse of list using parser for the elements.
149    /// Returns `None` if any of the elements fail to parse.
150    fn parse_list(
151        s: &str,
152        parse: fn(&str) -> Option<ParseVal>,
153    ) -> Option<ParseVal> {
154        let mut res = Vec::new();
155        for e in s.split(',') {
156            res.push(parse(e)?);
157        }
158        Some(ParseVal::List(res))
159    }
160
161    pub fn parse_bool(s: &str) -> Option<ParseVal> {
162        match s {
163            "true" => Some(ParseVal::Bool(true)),
164            "false" => Some(ParseVal::Bool(false)),
165            _ => None,
166        }
167    }
168
169    /// Parse a number from a string.
170    pub fn parse_num(s: &str) -> Option<ParseVal> {
171        s.parse::<i64>().ok().map(ParseVal::Num)
172    }
173
174    /// Parse a String from a string.
175    pub fn parse_string(s: &str) -> Option<ParseVal> {
176        Some(ParseVal::String(s.to_string()))
177    }
178
179    /// Parse a list of numbers from a string.
180    pub fn parse_num_list(s: &str) -> Option<ParseVal> {
181        Self::parse_list(s, Self::parse_num)
182    }
183
184    pub fn parse_outstream(s: &str) -> Option<ParseVal> {
185        s.parse::<OutputFile>().ok().map(ParseVal::OutStream)
186    }
187}
188
189/// Trait that describes named things. Calling [`do_pass`](Visitor::do_pass) and [`do_pass_default`](Visitor::do_pass_default).
190/// require this to be implemented.
191///
192/// This has to be a separate trait from [`Visitor`] because these methods don't recieve `self` which
193/// means that it is impossible to create dynamic trait objects.
194pub trait Named {
195    /// The name of a pass. Is used for identifying passes.
196    fn name() -> &'static str;
197    /// A short description of the pass.
198    fn description() -> &'static str;
199    /// Set of options that can be passed to the pass.
200    /// The options contains a tuple of the option name and a description.
201    fn opts() -> Vec<PassOpt> {
202        vec![]
203    }
204}
205
206/// Trait defining method that can be used to construct a Visitor from an
207/// [ir::Context].
208/// This is useful when a pass needs to construct information using the context
209/// *before* visiting the components.
210///
211/// For passes that don't need to use the context, this trait can be automatically
212/// be derived from [Default].
213pub trait ConstructVisitor {
214    fn get_opts(ctx: &ir::Context) -> LinkedHashMap<&'static str, ParseVal>
215    where
216        Self: Named,
217    {
218        let opts = Self::opts();
219        let n = Self::name();
220        let mut values: LinkedHashMap<&'static str, ParseVal> = ctx
221            .extra_opts
222            .iter()
223            .filter_map(|opt| {
224                // The format is either -x pass:opt or -x pass:opt=val
225                let mut splits = opt.split(':');
226                if let Some(pass) = splits.next() {
227                    if pass == n {
228                        let mut splits = splits.next()?.split('=');
229                        let opt = splits.next()?.to_string();
230                        let Some(opt) = opts.iter().find(|o| o.name == opt) else {
231                            log::warn!("Ignoring unknown option for pass `{n}`: {opt}");
232                                return None;
233                        };
234                        let val = if let Some(v) = splits.next() {
235                            let Some(v) = opt.parse(v) else {
236                                log::warn!(
237                                    "Ignoring invalid value for option `{n}:{}`: {v}",
238                                    opt.name(),
239                                );
240                                return None;
241                            };
242                            v
243                        } else {
244                            ParseVal::Bool(true)
245                        };
246                        return Some((opt.name(), val));
247                    }
248                }
249                None
250            })
251            .collect();
252
253        if log::log_enabled!(log::Level::Debug) {
254            log::debug!(
255                "Extra options for {}: {}",
256                Self::name(),
257                values.iter().map(|(o, v)| format!("{o}->{v}")).join(", ")
258            );
259        }
260
261        // For all options that were not provided with values, fill in the defaults.
262        for opt in opts {
263            if !values.contains_key(opt.name()) {
264                values.insert(opt.name(), opt.default.clone());
265            }
266        }
267
268        values
269    }
270
271    /// Construct the visitor using information from the Context
272    fn from(_ctx: &ir::Context) -> CalyxResult<Self>
273    where
274        Self: Sized;
275
276    /// Clear the data stored in the visitor. Called before traversing the
277    /// next component by [ir::traversal::Visitor].
278    fn clear_data(&mut self);
279}
280
281/// Derive ConstructVisitor when [Default] is provided for a visitor.
282impl<T: Default + Sized + Visitor> ConstructVisitor for T {
283    fn from(_ctx: &ir::Context) -> CalyxResult<Self> {
284        Ok(T::default())
285    }
286
287    fn clear_data(&mut self) {
288        *self = T::default();
289    }
290}