circomspect_program_structure/intermediate_representation/
lifting.rs

1use crate::ast::{self, LogArgument};
2use crate::report::ReportCollection;
3
4use crate::ir;
5use crate::ir::declarations::{Declaration, Declarations};
6use crate::ir::errors::{IRError, IRResult};
7use crate::nonempty_vec::NonEmptyVec;
8
9/// The `TryLift` trait is used to lift an AST node to an IR node. This may fail
10/// and produce an error. Even if the operation succeeds it may produce warnings
11/// which in this case are added to the report collection.
12pub(crate) trait TryLift<Context> {
13    type IR;
14    type Error;
15
16    /// Generate a corresponding IR node of type `Self::IR` from an AST node.
17    fn try_lift(
18        &self,
19        context: Context,
20        reports: &mut ReportCollection,
21    ) -> Result<Self::IR, Self::Error>;
22}
23
24#[derive(Default)]
25pub(crate) struct LiftingEnvironment {
26    /// Tracks all variable declarations.
27    declarations: Declarations,
28}
29
30impl LiftingEnvironment {
31    #[must_use]
32    pub fn new() -> LiftingEnvironment {
33        LiftingEnvironment::default()
34    }
35
36    pub fn add_declaration(&mut self, declaration: &Declaration) {
37        self.declarations.add_declaration(declaration);
38    }
39}
40
41impl From<LiftingEnvironment> for Declarations {
42    fn from(env: LiftingEnvironment) -> Declarations {
43        env.declarations
44    }
45}
46
47// Attempt to convert an AST statement into an IR statement. This will fail on
48// statements that need to be handled manually (like `While`, `IfThenElse`, and
49// `MultiSubstitution`), as well as statements that have no direct IR
50// counterparts (like `Declaration`, `Block`, and `InitializationBlock`).
51impl TryLift<()> for ast::Statement {
52    type IR = ir::Statement;
53    type Error = IRError;
54
55    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {
56        match self {
57            ast::Statement::Return { meta, value } => Ok(ir::Statement::Return {
58                meta: meta.try_lift((), reports)?,
59                value: value.try_lift((), reports)?,
60            }),
61            ast::Statement::Substitution { meta, var, op, rhe, access } => {
62                // If this is an array or component signal assignment (i.e. when access
63                // is non-empty), the RHS is lifted to an `Update` expression.
64                let rhe = if access.is_empty() {
65                    rhe.try_lift((), reports)?
66                } else {
67                    ir::Expression::Update {
68                        meta: meta.try_lift((), reports)?,
69                        var: var.try_lift(meta, reports)?,
70                        access: access
71                            .iter()
72                            .map(|access| access.try_lift((), reports))
73                            .collect::<IRResult<Vec<ir::AccessType>>>()?,
74                        rhe: Box::new(rhe.try_lift((), reports)?),
75                    }
76                };
77                Ok(ir::Statement::Substitution {
78                    meta: meta.try_lift((), reports)?,
79                    var: var.try_lift(meta, reports)?,
80                    op: op.try_lift((), reports)?,
81                    rhe,
82                })
83            }
84            ast::Statement::ConstraintEquality { meta, lhe, rhe } => {
85                Ok(ir::Statement::ConstraintEquality {
86                    meta: meta.try_lift((), reports)?,
87                    lhe: lhe.try_lift((), reports)?,
88                    rhe: rhe.try_lift((), reports)?,
89                })
90            }
91            ast::Statement::LogCall { meta, args } => Ok(ir::Statement::LogCall {
92                meta: meta.try_lift((), reports)?,
93                args: args
94                    .iter()
95                    .map(|arg| arg.try_lift((), reports))
96                    .collect::<IRResult<Vec<_>>>()?,
97            }),
98            ast::Statement::Assert { meta, arg } => Ok(ir::Statement::Assert {
99                meta: meta.try_lift((), reports)?,
100                arg: arg.try_lift((), reports)?,
101            }),
102            ast::Statement::Declaration { meta, xtype, name, dimensions, .. } => {
103                Ok(ir::Statement::Declaration {
104                    meta: meta.try_lift((), reports)?,
105                    names: NonEmptyVec::new(name.try_lift(meta, reports)?),
106                    var_type: xtype.try_lift((), reports)?,
107                    dimensions: dimensions
108                        .iter()
109                        .map(|size| size.try_lift((), reports))
110                        .collect::<IRResult<Vec<_>>>()?,
111                })
112            }
113            ast::Statement::Block { .. }
114            | ast::Statement::While { .. }
115            | ast::Statement::IfThenElse { .. }
116            | ast::Statement::MultiSubstitution { .. }
117            | ast::Statement::InitializationBlock { .. } => {
118                // These need to be handled by the caller.
119                panic!("failed to convert AST statement to IR")
120            }
121        }
122    }
123}
124
125// Attempt to convert an AST expression to an IR expression. This will fail on
126// expressions that need to be handled directly by the caller (like `Tuple` and
127// `AnonymousComponent`).
128impl TryLift<()> for ast::Expression {
129    type IR = ir::Expression;
130    type Error = IRError;
131
132    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {
133        match self {
134            ast::Expression::InfixOp { meta, lhe, infix_op, rhe } => Ok(ir::Expression::InfixOp {
135                meta: meta.try_lift((), reports)?,
136                lhe: Box::new(lhe.try_lift((), reports)?),
137                infix_op: infix_op.try_lift((), reports)?,
138                rhe: Box::new(rhe.try_lift((), reports)?),
139            }),
140            ast::Expression::PrefixOp { meta, prefix_op, rhe } => Ok(ir::Expression::PrefixOp {
141                meta: meta.try_lift((), reports)?,
142                prefix_op: prefix_op.try_lift((), reports)?,
143                rhe: Box::new(rhe.try_lift((), reports)?),
144            }),
145            ast::Expression::InlineSwitchOp { meta, cond, if_true, if_false } => {
146                Ok(ir::Expression::SwitchOp {
147                    meta: meta.try_lift((), reports)?,
148                    cond: Box::new(cond.try_lift((), reports)?),
149                    if_true: Box::new(if_true.try_lift((), reports)?),
150                    if_false: Box::new(if_false.try_lift((), reports)?),
151                })
152            }
153            ast::Expression::Variable { meta, name, access } => {
154                if access.is_empty() {
155                    Ok(ir::Expression::Variable {
156                        meta: meta.try_lift((), reports)?,
157                        name: name.try_lift(meta, reports)?,
158                    })
159                } else {
160                    Ok(ir::Expression::Access {
161                        meta: meta.try_lift((), reports)?,
162                        var: name.try_lift(meta, reports)?,
163                        access: access
164                            .iter()
165                            .map(|access| access.try_lift((), reports))
166                            .collect::<IRResult<Vec<ir::AccessType>>>()?,
167                    })
168                }
169            }
170            ast::Expression::Number(meta, value) => {
171                Ok(ir::Expression::Number(meta.try_lift((), reports)?, value.clone()))
172            }
173            ast::Expression::Call { meta, id, args } => Ok(ir::Expression::Call {
174                meta: meta.try_lift((), reports)?,
175                name: id.clone(),
176                args: args
177                    .iter()
178                    .map(|arg| arg.try_lift((), reports))
179                    .collect::<IRResult<Vec<ir::Expression>>>()?,
180            }),
181            ast::Expression::ArrayInLine { meta, values } => Ok(ir::Expression::InlineArray {
182                meta: meta.try_lift((), reports)?,
183                values: values
184                    .iter()
185                    .map(|value| value.try_lift((), reports))
186                    .collect::<IRResult<Vec<ir::Expression>>>()?,
187            }),
188            // TODO: We currently treat `ParallelOp` as transparent and simply
189            // lift the underlying expression. Should this be added to the IR?
190            ast::Expression::ParallelOp { rhe, .. } => rhe.try_lift((), reports),
191            ast::Expression::Tuple { .. } | ast::Expression::AnonymousComponent { .. } => {
192                // These need to be handled by the caller.
193                panic!("failed to convert AST expression to IR")
194            }
195        }
196    }
197}
198
199// Convert AST metadata to IR metadata. (This will always succeed.)
200impl TryLift<()> for ast::Meta {
201    type IR = ir::Meta;
202    type Error = IRError;
203
204    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {
205        Ok(ir::Meta::new(&self.location, &self.file_id))
206    }
207}
208
209// Convert an AST variable type to an IR type. (This will always succeed.)
210impl TryLift<()> for ast::VariableType {
211    type IR = ir::VariableType;
212    type Error = IRError;
213
214    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {
215        match self {
216            ast::VariableType::Var => Ok(ir::VariableType::Local),
217            ast::VariableType::Component => Ok(ir::VariableType::Component),
218            ast::VariableType::AnonymousComponent => Ok(ir::VariableType::AnonymousComponent),
219            ast::VariableType::Signal(signal_type, tag_list) => {
220                Ok(ir::VariableType::Signal(signal_type.try_lift((), reports)?, tag_list.clone()))
221            }
222        }
223    }
224}
225
226// Convert an AST signal type to an IR signal type. (This will always succeed.)
227impl TryLift<()> for ast::SignalType {
228    type IR = ir::SignalType;
229    type Error = IRError;
230
231    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {
232        match self {
233            ast::SignalType::Input => Ok(ir::SignalType::Input),
234            ast::SignalType::Output => Ok(ir::SignalType::Output),
235            ast::SignalType::Intermediate => Ok(ir::SignalType::Intermediate),
236        }
237    }
238}
239
240// Attempt to convert a string to an IR variable name.
241impl TryLift<&ast::Meta> for String {
242    type IR = ir::VariableName;
243    type Error = IRError;
244
245    fn try_lift(&self, meta: &ast::Meta, _: &mut ReportCollection) -> IRResult<Self::IR> {
246        // We assume that the input string uses '.' to separate the name from the suffix.
247        let tokens: Vec<_> = self.split('.').collect();
248        match tokens.len() {
249            1 => Ok(ir::VariableName::from_string(tokens[0])),
250            2 => Ok(ir::VariableName::from_string(tokens[0]).with_suffix(tokens[1])),
251            // Either the original name from the AST contains `.`, or the suffix
252            // added when ensuring uniqueness contains `.`. Neither case should
253            // occur, so we return an error here instead of producing a report.
254            _ => Err(IRError::InvalidVariableNameError {
255                name: self.clone(),
256                file_id: meta.file_id,
257                file_location: meta.location.clone(),
258            }),
259        }
260    }
261}
262
263// Convert an AST access to an IR access. (This will always succeed.)
264impl TryLift<()> for ast::Access {
265    type IR = ir::AccessType;
266    type Error = IRError;
267
268    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {
269        match self {
270            ast::Access::ArrayAccess(expr) => {
271                Ok(ir::AccessType::ArrayAccess(Box::new(expr.try_lift((), reports)?)))
272            }
273            ast::Access::ComponentAccess(s) => Ok(ir::AccessType::ComponentAccess(s.clone())),
274        }
275    }
276}
277
278// Convert an AST assignment to an IR assignment. (This will always succeed.)
279impl TryLift<()> for ast::AssignOp {
280    type IR = ir::AssignOp;
281    type Error = IRError;
282
283    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {
284        match self {
285            ast::AssignOp::AssignSignal => Ok(ir::AssignOp::AssignSignal),
286            ast::AssignOp::AssignVar => Ok(ir::AssignOp::AssignLocalOrComponent),
287            ast::AssignOp::AssignConstraintSignal => Ok(ir::AssignOp::AssignConstraintSignal),
288        }
289    }
290}
291
292// Convert an AST opcode to an IR opcode. (This will always succeed.)
293impl TryLift<()> for ast::ExpressionPrefixOpcode {
294    type IR = ir::ExpressionPrefixOpcode;
295    type Error = IRError;
296
297    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {
298        match self {
299            ast::ExpressionPrefixOpcode::Sub => Ok(ir::ExpressionPrefixOpcode::Sub),
300            ast::ExpressionPrefixOpcode::BoolNot => Ok(ir::ExpressionPrefixOpcode::BoolNot),
301            ast::ExpressionPrefixOpcode::Complement => Ok(ir::ExpressionPrefixOpcode::Complement),
302        }
303    }
304}
305
306// Convert an AST opcode to an IR opcode. (This will always succeed.)
307impl TryLift<()> for ast::ExpressionInfixOpcode {
308    type IR = ir::ExpressionInfixOpcode;
309    type Error = IRError;
310
311    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {
312        match self {
313            ast::ExpressionInfixOpcode::Mul => Ok(ir::ExpressionInfixOpcode::Mul),
314            ast::ExpressionInfixOpcode::Div => Ok(ir::ExpressionInfixOpcode::Div),
315            ast::ExpressionInfixOpcode::Add => Ok(ir::ExpressionInfixOpcode::Add),
316            ast::ExpressionInfixOpcode::Sub => Ok(ir::ExpressionInfixOpcode::Sub),
317            ast::ExpressionInfixOpcode::Pow => Ok(ir::ExpressionInfixOpcode::Pow),
318            ast::ExpressionInfixOpcode::IntDiv => Ok(ir::ExpressionInfixOpcode::IntDiv),
319            ast::ExpressionInfixOpcode::Mod => Ok(ir::ExpressionInfixOpcode::Mod),
320            ast::ExpressionInfixOpcode::ShiftL => Ok(ir::ExpressionInfixOpcode::ShiftL),
321            ast::ExpressionInfixOpcode::ShiftR => Ok(ir::ExpressionInfixOpcode::ShiftR),
322            ast::ExpressionInfixOpcode::LesserEq => Ok(ir::ExpressionInfixOpcode::LesserEq),
323            ast::ExpressionInfixOpcode::GreaterEq => Ok(ir::ExpressionInfixOpcode::GreaterEq),
324            ast::ExpressionInfixOpcode::Lesser => Ok(ir::ExpressionInfixOpcode::Lesser),
325            ast::ExpressionInfixOpcode::Greater => Ok(ir::ExpressionInfixOpcode::Greater),
326            ast::ExpressionInfixOpcode::Eq => Ok(ir::ExpressionInfixOpcode::Eq),
327            ast::ExpressionInfixOpcode::NotEq => Ok(ir::ExpressionInfixOpcode::NotEq),
328            ast::ExpressionInfixOpcode::BoolOr => Ok(ir::ExpressionInfixOpcode::BoolOr),
329            ast::ExpressionInfixOpcode::BoolAnd => Ok(ir::ExpressionInfixOpcode::BoolAnd),
330            ast::ExpressionInfixOpcode::BitOr => Ok(ir::ExpressionInfixOpcode::BitOr),
331            ast::ExpressionInfixOpcode::BitAnd => Ok(ir::ExpressionInfixOpcode::BitAnd),
332            ast::ExpressionInfixOpcode::BitXor => Ok(ir::ExpressionInfixOpcode::BitXor),
333        }
334    }
335}
336
337impl TryLift<()> for LogArgument {
338    type IR = ir::LogArgument;
339    type Error = IRError;
340
341    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {
342        match self {
343            ast::LogArgument::LogStr(message) => Ok(ir::LogArgument::String(message.clone())),
344            ast::LogArgument::LogExp(value) => {
345                Ok(ir::LogArgument::Expr(Box::new(value.try_lift((), reports)?)))
346            }
347        }
348    }
349}
350
351#[cfg(test)]
352mod tests {
353    use proptest::prelude::*;
354
355    use crate::report::ReportCollection;
356
357    use super::*;
358
359    proptest! {
360        #[test]
361        fn variable_name_from_string(name in "[$_]*[a-zA-Z][a-zA-Z$_0-9]*") {
362            let meta = ast::Meta::new(0, 1);
363            let mut reports = ReportCollection::new();
364
365            let var = name.try_lift(&meta, &mut reports).unwrap();
366            assert!(var.suffix().is_none());
367            assert!(var.version().is_none());
368            assert!(reports.is_empty());
369        }
370
371        #[test]
372        fn variable_name_with_suffix_from_string(name in "[$_]*[a-zA-Z][a-zA-Z$_0-9]*\\.[a-zA-Z$_0-9]*") {
373            let meta = ast::Meta::new(0, 1);
374            let mut reports = ReportCollection::new();
375
376            let var = name.try_lift(&meta, &mut reports).unwrap();
377            assert!(var.suffix().is_some());
378            assert!(var.version().is_none());
379            assert!(reports.is_empty());
380        }
381
382        #[test]
383        fn variable_name_from_invalid_string(name in "[$_]*[a-zA-Z][a-zA-Z$_0-9]*\\.[a-zA-Z$_0-9]*\\.[a-zA-Z$_0-9]*") {
384            let meta = ast::Meta::new(0, 1);
385            let mut reports = ReportCollection::new();
386
387            let result = name.try_lift(&meta, &mut reports);
388            assert!(result.is_err());
389            assert!(reports.is_empty());
390        }
391    }
392}