openqasm_parser/openqasm/
semantic_analysis.rs

1//! The code which performs semantic analyzis on an AST according to
2//! the openqasm 2.0 specification.
3
4mod misc;
5
6use self::misc::{arg_id, arg_to_id, is_unique};
7use crate::openqasm::ast::{
8    ast_to_vec::UOpOrBarrier,
9    AnyList, Argument, Decl, Exp, Exp1, Exp2, Exp3, Exp4, GateDecl, GopList, Identifier,
10    MainProgram, QOp, Statement, UOp,
11    UnaryOp::{Cos, Exp as Exponent, Ln, Sin, Sqrt, Tan},
12};
13use crate::parser::ast::ast_node::{ToRefVec, ToVec};
14use std::{cmp::Eq, collections::HashMap, hash::Hash};
15
16/// The type returned after performing semantic analysis on an AST.
17///
18/// It contains the quantum and classical registers and the gates defined
19/// in the program. I also contains a list of the [Operation]s to perform in
20/// order. The [Operation]s are optionally paired with a [Condition] for running.
21/// the operation.
22pub struct OpenQASMProgram {
23    /// The custom gates defined in the openqasm program. Maps the gate names
24    /// to [Gate]s
25    pub gates: HashMap<String, Gate>,
26
27    /// The quantum registers defined in the openqasm program. Maps the register names
28    /// to the size of the register.
29    pub qregs: HashMap<String, usize>,
30
31    /// The classical registers defined in the openqasm program. Maps the register names
32    /// to the size of the register.
33    pub cregs: HashMap<String, usize>,
34
35    /// The [Operation]s to execute when running the openqasm program. The operations are
36    /// optionally paired with a [Condition] which controls whether to run the operation or not.
37    pub operations: Vec<(Option<Condition>, Operation)>,
38}
39
40/// Describes a custom gate defined in openqasm
41pub struct Gate {
42    /// How many qubits the gate operates on
43    pub num_targets: usize,
44
45    /// How many parameters the gate requires
46    pub num_arguments: usize,
47
48    /// The list of [GateOperation]s to apply when running the gate in order of execution.
49    pub operations: Vec<GateOperation>,
50}
51
52/// When defining a gate they can take parameter variables. These variables can then
53/// be used in the gate definition to create an expression which can be evaluated into
54/// a number. However when defining the gate the parameter variables do not have a specified
55/// value so [GateOperation]s can not simply store [f32] as parameters, they need to contain
56/// functions which along with some values for the parameter variables produce a [f32].
57pub type Expression = dyn Fn(&Vec<f32>) -> f32;
58
59/// Operations which can be used inside of a gate declaration in openqasm. This is a subset of [Operation].
60/// Unlike [Operation], however, they contain [Expression]s instead of [f32] (for reasons described in the [Expression]
61/// documentation) and [usize] instead of [Qubit]. The [usize] arguments specify which qubit to use from the
62/// gates argument list, starting from the first one at index zero.
63pub enum GateOperation {
64    U(Box<Expression>, Box<Expression>, Box<Expression>, usize),
65    CX(usize, usize),
66    Custom(String, Vec<Box<Expression>>, Vec<usize>),
67}
68
69/// A qubit defined in an openqasm program. Contains the name of the register containing
70/// the qubit, as well as the index of the qubit in the register.
71#[derive(Clone, PartialEq, Eq, Hash, Debug)]
72pub struct Qubit(pub String, pub usize);
73
74/// A classical bit defined in an openqasm program. Contains the name of the register containing
75/// the bit, as well as the index of the bit in the register.
76#[derive(Clone, Debug)]
77pub struct Cbit(pub String, pub usize);
78
79/// A condition that determines whether to perform an [Operation] or not. The first parameter
80/// is the name to a classical register. The second parameter is a value to compare the register
81/// to when interpreting the register as a binary number with index 0 as the least significan bit.
82/// When the register is equal to the second parameter the operation should be applied.
83#[derive(Debug, Clone)]
84pub struct Condition(pub String, pub u32);
85
86/// An operation to run when running an openqasm program.
87#[derive(Debug)]
88pub enum Operation {
89    /// The general unary gate supported by openqasm. Takes 3 parameters an a qubit target.
90    U(f32, f32, f32, Qubit),
91
92    /// The CX/CNot gate. The first argument is the control bit, and the second is the target.
93    CX(Qubit, Qubit),
94
95    /// A custom gate defined in the openqasm program. Contains the name of the gate, the parameters
96    /// used when calling the gate, and the qubit targets. When returned from [OpenQASMProgram::from_ast]
97    /// it is guaranteed that there is a gate matching the name given in the first field. This gate
98    /// is also guaranteed to take as many parameters and arguments as is stored in this datatype.
99    Custom(String, Vec<f32>, Vec<Qubit>),
100
101    /// Operation for measuring a qubit onto a classical bit.
102    Measure(Qubit, Cbit),
103
104    /// Operation for reseting a qubit to its zero state |0>.
105    ResetQ(Qubit),
106
107    /// Operation for reseting a classical bit to its zero state 0.
108    ResetC(Cbit),
109}
110
111/// The different errors that can occur when running semantic analysis on an AST.
112#[derive(Debug)]
113pub enum SemanticError {
114    /// Only openqasm 2.0 is supported, so the first line has to be: OPENQASM 2.0;
115    UnsupportedVersion,
116
117    /// Two registers have been defined with the same name.
118    DuplicateRegisterDeclaration,
119
120    /// An operation was applied with the wrong number of parameters.
121    WrongNumberOfParameters,
122
123    /// An operation was applied with the wrong number of qubits.
124    WrongNumberOfTargets,
125
126    /// An identifier was used which had not been previously defined.
127    UnknownIdentifier,
128
129    /// Inside of a gate declaration you are only allowed to use the parameters and
130    /// arguments defined in the gate declaration. The arguments are not allowed to be
131    /// indexed inside the gate definition, otherwise this error is returned.
132    IndexedRegisterInGateDecl,
133
134    /// An operation was applied to some qubits which contained the same qubit several times.
135    SameTargetUsedTwice,
136
137    /// An operation can be called on entire registers or on single qubits. When more than one
138    /// of the arguments specify an entire register then all these registers have to have the same
139    /// size. Otherwise this error is returned.
140    InvalidTargetDimensions,
141
142    /// A register was indexed outside of its range [0..size)
143    IndexOutOfBounds,
144
145    /// An opaque gate is one which doesn't have a definition. This is currently not supported.
146    OpaqueIsNotSupported,
147}
148
149impl OpenQASMProgram {
150    /// The main function for running semantic analysis on an AST.
151    pub fn from_ast(ast_node: &MainProgram) -> Result<Self, SemanticError> {
152        let mut openqasm_program = OpenQASMProgram {
153            gates: HashMap::new(),
154            qregs: HashMap::new(),
155            cregs: HashMap::new(),
156            operations: Vec::new(),
157        };
158
159        // Only openqasm version 2.0 is supported
160        if ast_node.0 .0 != "2.0" {
161            return Err(SemanticError::UnsupportedVersion);
162        }
163
164        for statement in ast_node.1.to_ref_vec() {
165            match statement {
166                Statement::Decl(decl) => openqasm_program.create_reg(decl)?,
167                Statement::GateDecl(gatedecl, goplist) => {
168                    openqasm_program.create_gate(gatedecl, Some(goplist))?
169                }
170                Statement::GateDeclEmpty(gatedecl) => {
171                    openqasm_program.create_gate(gatedecl, None)?
172                }
173                Statement::Opaque => return Err(SemanticError::OpaqueIsNotSupported),
174                Statement::QOp(qop) => {
175                    openqasm_program.create_qop(qop, None)?;
176                }
177                Statement::If(id, int, qop) => {
178                    if !openqasm_program.cregs.contains_key(&id.0) {
179                        return Err(SemanticError::UnknownIdentifier);
180                    }
181                    let condition = Some(Condition(id.0.clone(), int.0));
182                    openqasm_program.create_qop(qop, condition)?;
183                }
184                Statement::Barrier(_) => {}
185            };
186        }
187
188        Ok(openqasm_program)
189    }
190
191    /// Validates a register declaration and adds the new register to self.qregs or
192    /// self.cregs if the declaration was valid.
193    fn create_reg(&mut self, decl: &Decl) -> Result<(), SemanticError> {
194        let res = match decl {
195            Decl::QReg(id, size) => self.qregs.insert(id.0.clone(), size.0 as usize),
196            Decl::CReg(id, size) => self.cregs.insert(id.0.clone(), size.0 as usize),
197        };
198        if res.is_some() {
199            return Err(SemanticError::DuplicateRegisterDeclaration);
200        }
201        Ok(())
202    }
203
204    /// Validates a gate declaration and adds the new gate along with its associated operations
205    /// to self.gates.
206    fn create_gate(
207        &mut self,
208        gatedecl: &GateDecl,
209        goplist: Option<&GopList>,
210    ) -> Result<(), SemanticError> {
211        // Get the name params and targets
212        let (name, params, targets) = match gatedecl {
213            GateDecl::NoArgList(name, targets) => (name, vec![], targets.to_ref_vec()),
214            GateDecl::EmptyArgList(name, targets) => (name, vec![], targets.to_ref_vec()),
215            GateDecl::WithArgList(name, params, targets) => {
216                (name, params.to_ref_vec(), targets.to_ref_vec())
217            }
218        };
219
220        // Get the gate operations
221        let mut gate_ops = Vec::new();
222        let goplist = goplist.map_or(vec![], |gl| gl.to_vec());
223        for gop in goplist {
224            match gop {
225                UOpOrBarrier::UOp(UOp::U(exps, target)) => {
226                    let exps = exps.to_ref_vec();
227                    if exps.len() != 3 {
228                        return Err(SemanticError::WrongNumberOfParameters);
229                    }
230                    let exp1 = create_exp(exps[0], &params)?;
231                    let exp2 = create_exp(exps[1], &params)?;
232                    let exp3 = create_exp(exps[2], &params)?;
233
234                    let target = create_gate_targets(&vec![arg_to_id(&target)?], &targets)?;
235
236                    gate_ops.push(GateOperation::U(exp1, exp2, exp3, target[0]));
237                }
238                UOpOrBarrier::UOp(UOp::CX(target1, target2)) => {
239                    let op_targets = create_gate_targets(
240                        &vec![arg_to_id(&target1)?, arg_to_id(&target2)?],
241                        &targets,
242                    )?;
243
244                    gate_ops.push(GateOperation::CX(op_targets[0], op_targets[1]));
245                }
246                UOpOrBarrier::UOp(UOp::NoArgList(op_name, op_targets)) => {
247                    gate_ops.push(create_custom_gate_op(
248                        &params,
249                        &op_name,
250                        vec![],
251                        &op_targets,
252                        &targets,
253                        &self.gates,
254                    )?);
255                }
256                UOpOrBarrier::UOp(UOp::EmptyArgList(op_name, op_targets)) => {
257                    gate_ops.push(create_custom_gate_op(
258                        &params,
259                        &op_name,
260                        vec![],
261                        &op_targets,
262                        &targets,
263                        &self.gates,
264                    )?);
265                }
266                UOpOrBarrier::UOp(UOp::WithArgList(op_name, op_args, op_targets)) => {
267                    gate_ops.push(create_custom_gate_op(
268                        &params,
269                        &op_name,
270                        op_args.to_ref_vec(),
271                        &op_targets,
272                        &targets,
273                        &self.gates,
274                    )?);
275                }
276                UOpOrBarrier::Barrier(_) => {} // Barriers are ignored (for now atleast)
277            }
278        }
279
280        self.gates.insert(
281            name.0.clone(),
282            Gate {
283                num_targets: targets.len(),
284                num_arguments: params.len(),
285                operations: gate_ops,
286            },
287        );
288        Ok(())
289    }
290
291    /// Validates a quantum operation AST node and an optional condition and adds a new [Operation] to
292    /// self.operations if they are valid.
293    fn create_qop(&mut self, qop: &QOp, condition: Option<Condition>) -> Result<(), SemanticError> {
294        match qop {
295            QOp::UOp(uop) => self.create_uop(uop, condition)?,
296            QOp::Measure(qubit, cbit) => {
297                let qbits = create_uop_targets(&vec![qubit.clone()], &self.qregs)?;
298                let cbits = create_uop_targets(&vec![cbit.clone()], &self.cregs)?;
299                if qbits.len() != cbits.len() {
300                    return Err(SemanticError::InvalidTargetDimensions);
301                }
302                for (qubit, cbit) in qbits.iter().zip(cbits.iter()) {
303                    let cbit = Cbit(cbit[0].0.clone(), cbit[0].1);
304                    self.operations.push((
305                        condition.clone(),
306                        Operation::Measure(qubit[0].clone(), cbit),
307                    ));
308                }
309            }
310            QOp::Reset(bit) => {
311                let qubits = create_uop_targets(&vec![bit.clone()], &self.qregs);
312                if let Ok(qubits) = qubits {
313                    for qubits in qubits {
314                        self.operations
315                            .push((condition.clone(), Operation::ResetQ(qubits[0].clone())));
316                    }
317                } else {
318                    let cbits = create_uop_targets(&vec![bit.clone()], &self.cregs)?;
319                    for cbits in cbits {
320                        let cbit = Cbit(cbits[0].0.clone(), cbits[0].1);
321                        self.operations
322                            .push((condition.clone(), Operation::ResetC(cbit)));
323                    }
324                }
325            }
326        }
327        Ok(())
328    }
329
330    /// Used by [create_qop](OpenQASMProgram::create_qop) to handle gate applications.
331    fn create_uop(&mut self, uop: &UOp, condition: Option<Condition>) -> Result<(), SemanticError> {
332        match uop {
333            UOp::U(exps, target) => {
334                let exps: Result<Vec<_>, _> =
335                    exps.to_ref_vec().iter().map(|e| exp_to_float(e)).collect();
336                let exps = exps?;
337                if exps.len() != 3 {
338                    return Err(SemanticError::WrongNumberOfParameters);
339                }
340                let targets = create_uop_targets(&vec![target.clone()], &self.qregs)?;
341
342                for targets in targets.iter() {
343                    self.operations.push((
344                        condition.clone(),
345                        Operation::U(exps[0], exps[1], exps[2], targets[0].clone()),
346                    ));
347                }
348            }
349            UOp::CX(target1, target2) => {
350                let targets =
351                    create_uop_targets(&vec![target1.clone(), target2.clone()], &self.qregs)?;
352
353                for targets in targets.iter() {
354                    self.operations.push((
355                        condition.clone(),
356                        Operation::CX(targets[0].clone(), targets[1].clone()),
357                    ));
358                }
359            }
360            UOp::NoArgList(name, targets) => {
361                let targets = create_uop_targets(&targets.to_vec(), &self.qregs)?;
362                for targets in targets {
363                    self.operations.push((
364                        condition.clone(),
365                        create_cusom_uop(name, vec![], targets, &self.gates)?,
366                    ));
367                }
368            }
369            UOp::EmptyArgList(name, targets) => {
370                let targets = create_uop_targets(&targets.to_vec(), &self.qregs)?;
371                for targets in targets {
372                    self.operations.push((
373                        condition.clone(),
374                        create_cusom_uop(name, vec![], targets, &self.gates)?,
375                    ));
376                }
377            }
378            UOp::WithArgList(name, params, targets) => {
379                let targets = create_uop_targets(&targets.to_vec(), &self.qregs)?;
380                for targets in targets {
381                    self.operations.push((
382                        condition.clone(),
383                        create_cusom_uop(name, params.to_ref_vec(), targets, &self.gates)?,
384                    ));
385                }
386            }
387        }
388
389        Ok(())
390    }
391}
392
393/// Used by [OpenQASMProgram::create_gate] to validate and create a custom gate.
394fn create_custom_gate_op(
395    params: &Vec<&Identifier>, // The parameters defined in the gate definition.
396    op_name: &Identifier,      // The name of the gate operation.
397    op_params: Vec<&Exp>,      // The expressions passed in as parameters to the gate operation.
398    op_targets: &AnyList,      // The arguments applied to the gate operation.
399    target_names: &Vec<&Identifier>, // The arguments defined in the gate definition.
400    gates: &HashMap<String, Gate>, // All the previously defined gates.
401) -> Result<GateOperation, SemanticError> {
402    let op_name = &op_name.0;
403    let op_targets = match op_targets {
404        AnyList::IdList(op_targets) => op_targets.to_ref_vec(),
405        AnyList::MixedList(_) => Err(SemanticError::IndexedRegisterInGateDecl)?,
406    };
407
408    if let Some(gate) = gates.get(op_name) {
409        if gate.num_targets != op_targets.len() {
410            return Err(SemanticError::WrongNumberOfTargets);
411        }
412
413        if gate.num_arguments != op_params.len() {
414            return Err(SemanticError::WrongNumberOfParameters);
415        }
416    } else {
417        return Err(SemanticError::UnknownIdentifier);
418    }
419
420    let op_params: Result<Vec<_>, _> = op_params.iter().map(|a| create_exp(a, &params)).collect();
421    let op_params = op_params?;
422    let op_targets = create_gate_targets(&op_targets, target_names)?;
423
424    Ok(GateOperation::Custom(
425        op_name.clone(),
426        op_params,
427        op_targets,
428    ))
429}
430
431/// Creates an expression of priority 1.
432fn create_exp1(exp: &Exp1, exps: &Vec<&Identifier>) -> Result<Box<Expression>, SemanticError> {
433    match exp {
434        Exp1::Number(num) => {
435            let f = num.0.parse::<f32>().unwrap();
436            Ok(Box::new(move |_| f))
437        }
438        Exp1::Integer(int) => {
439            let i = int.0 as f32;
440            Ok(Box::new(move |_| i))
441        }
442        Exp1::Identifier(id) => {
443            let search = exps.iter().position(|&arg| arg.0 == id.0);
444            if let Some(i) = search {
445                Ok(Box::new(move |args| args[i]))
446            } else {
447                Err(SemanticError::UnknownIdentifier)
448            }
449        }
450        Exp1::Pi => Ok(Box::new(|_| std::f32::consts::PI)),
451        Exp1::Paren(exp) => create_exp(exp, exps),
452        Exp1::UnaryOp(uop, exp) => {
453            let exp = create_exp(exp, exps)?;
454            match uop {
455                Sin => Ok(Box::new(move |args| f32::sin(exp(args)))),
456                Cos => Ok(Box::new(move |args| f32::cos(exp(args)))),
457                Tan => Ok(Box::new(move |args| f32::tan(exp(args)))),
458                Exponent => Ok(Box::new(move |args| f32::exp(exp(args)))),
459                Ln => Ok(Box::new(move |args| f32::ln(exp(args)))),
460                Sqrt => Ok(Box::new(move |args| f32::sqrt(exp(args)))),
461            }
462        }
463        Exp1::Neg(exp) => {
464            let exp = create_exp(exp, exps)?;
465            Ok(Box::new(move |args| -exp(args)))
466        }
467    }
468}
469
470/// Creates an expression of priority 2.
471fn create_exp2(exp: &Exp2, exps: &Vec<&Identifier>) -> Result<Box<Expression>, SemanticError> {
472    match exp {
473        Exp2::Pow(lhs, rhs) => {
474            let lhs = create_exp1(lhs, exps)?;
475            let rhs = create_exp2(rhs, exps)?;
476            Ok(Box::new(move |args| lhs(args).powf(rhs(args))))
477        }
478        Exp2::Exp1(exp1) => create_exp1(exp1, exps),
479    }
480}
481
482/// Creates an expression of priority 3.
483fn create_exp3(exp: &Exp3, exps: &Vec<&Identifier>) -> Result<Box<Expression>, SemanticError> {
484    match exp {
485        Exp3::Mul(lhs, rhs) => {
486            let lhs = create_exp2(lhs, exps)?;
487            let rhs = create_exp3(rhs, exps)?;
488            Ok(Box::new(move |args| lhs(args) * rhs(args)))
489        }
490        Exp3::Div(lhs, rhs) => {
491            let lhs = create_exp2(lhs, exps)?;
492            let rhs = create_exp3(rhs, exps)?;
493            Ok(Box::new(move |args| lhs(args) / rhs(args)))
494        }
495        Exp3::Exp2(exp2) => create_exp2(exp2, exps),
496    }
497}
498
499/// Creates an expression of priority 4.
500fn create_exp4(exp: &Exp4, exps: &Vec<&Identifier>) -> Result<Box<Expression>, SemanticError> {
501    match exp {
502        Exp4::Add(lhs, rhs) => {
503            let lhs = create_exp3(lhs, exps)?;
504            let rhs = create_exp4(rhs, exps)?;
505            Ok(Box::new(move |args| lhs(args) + rhs(args)))
506        }
507        Exp4::Sub(lhs, rhs) => {
508            let lhs = create_exp3(lhs, exps)?;
509            let rhs = create_exp4(rhs, exps)?;
510            Ok(Box::new(move |args| lhs(args) - rhs(args)))
511        }
512        Exp4::Exp3(exp3) => create_exp3(exp3, exps),
513    }
514}
515
516/// Creates an arithmetic expression which can contain some variables.
517/// These expressions depend on the values of the variables, which is not known at time of
518/// construction. Therefore, an [Expression] is returned which takes some values for
519/// the variables and returns the value of the expression.
520fn create_exp(exp: &Exp, exps: &Vec<&Identifier>) -> Result<Box<Expression>, SemanticError> {
521    create_exp4(exp.0.as_ref(), exps)
522}
523
524/// Validates a list of targets which will be applied to a gate operation. These should be some
525/// subset of the targets defined in the gate declaration.
526fn create_gate_targets(
527    targets: &Vec<&Identifier>,
528    gate_targets: &Vec<&Identifier>,
529) -> Result<Vec<usize>, SemanticError> {
530    let res: Result<Vec<_>, _> = targets
531        .iter()
532        .map(|target| {
533            gate_targets
534                .iter()
535                .position(|t| t.0 == target.0)
536                .ok_or(SemanticError::UnknownIdentifier)
537        })
538        .collect();
539    let res = res?;
540    if is_unique(&res) {
541        Ok(res)
542    } else {
543        Err(SemanticError::SameTargetUsedTwice)
544    }
545}
546
547/// Converts an expression which doesn't contain variables into an [f32]. This is
548/// used in operations outside of gate definitions, because here there are no variables
549/// to use.
550fn exp_to_float(exp: &Exp) -> Result<f32, SemanticError> {
551    let exp_fn = create_exp(exp, &vec![])?;
552    Ok(exp_fn(&vec![]))
553}
554
555/// Converts a list of targets which will be applied to an operations and converts it
556/// into [Qubit]s which reference some previously defined register.
557fn create_uop_targets(
558    targets: &Vec<Argument>,
559    regs: &HashMap<String, usize>,
560) -> Result<Vec<Vec<Qubit>>, SemanticError> {
561    // Check that all the target registers have the same size
562    let mut arg_len = None;
563    for target in targets.iter() {
564        let target_len = regs
565            .get(arg_id(target))
566            .ok_or(SemanticError::UnknownIdentifier)?;
567
568        match target {
569            Argument::Id(_) => {
570                if let Some(arg_len) = arg_len {
571                    if arg_len != target_len {
572                        return Err(SemanticError::InvalidTargetDimensions);
573                    }
574                } else {
575                    arg_len = Some(target_len);
576                }
577            }
578            Argument::Indexed(_, index) => {
579                if index.0 as usize >= *target_len {
580                    return Err(SemanticError::IndexOutOfBounds);
581                }
582            }
583        }
584    }
585    let arg_len = *arg_len.unwrap_or(&1);
586
587    let mut res = vec![];
588    for i in 0..arg_len {
589        let mut row = Vec::new();
590
591        for target in targets.iter() {
592            let qubit = match target {
593                Argument::Id(id) => Qubit(id.0.clone(), i),
594                Argument::Indexed(id, index) => Qubit(id.0.clone(), index.0 as usize),
595            };
596
597            row.push(qubit);
598        }
599
600        if !is_unique(&row) {
601            return Err(SemanticError::SameTargetUsedTwice);
602        }
603
604        res.push(row);
605    }
606
607    Ok(res)
608}
609
610/// Creates an [Operation] which applies a custom gate.
611fn create_cusom_uop(
612    name: &Identifier,             // The name of the gate
613    params: Vec<&Exp>, // The parameters. Should not contain any variables and can therefore be evaluated into f32s
614    targets: Vec<Qubit>, // The qubits to apply the gate to
615    gates: &HashMap<String, Gate>, // The previously defined gates
616) -> Result<Operation, SemanticError> {
617    let params: Result<Vec<_>, _> = params.to_vec().iter().map(|e| exp_to_float(e)).collect();
618    let params = params?;
619
620    let gate = gates.get(&name.0).ok_or(SemanticError::UnknownIdentifier)?;
621    if params.len() != gate.num_arguments {
622        return Err(SemanticError::WrongNumberOfParameters);
623    }
624    if targets.len() != gate.num_targets {
625        return Err(SemanticError::WrongNumberOfTargets);
626    }
627
628    Ok(Operation::Custom(name.0.clone(), params, targets))
629}