quil_rs/program/
type_check.rs

1//! Type check Quil programs.
2//!
3//! See the [Quil spec](https://quil-lang.github.io/).
4
5// TODO (#452): Address large result types.
6#![allow(clippy::result_large_err)]
7
8use std::fmt::Debug;
9
10use indexmap::IndexMap;
11use thiserror::Error;
12
13use crate::{
14    expression::{Expression, FunctionCallExpression, InfixExpression, PrefixExpression},
15    instruction::{
16        Arithmetic, ArithmeticOperand, ArithmeticOperator, BinaryLogic, BinaryOperand,
17        BinaryOperator, Comparison, ComparisonOperand, ComparisonOperator, Exchange, Instruction,
18        Load, MemoryReference, Move, ScalarType, SetFrequency, SetPhase, SetScale, ShiftFrequency,
19        ShiftPhase, Store, UnaryLogic, UnaryOperator,
20    },
21    program::MemoryRegion,
22    quil::Quil,
23    Program,
24};
25
26/// Different types of errors that can occur during type checking.
27#[derive(Debug, Error)]
28pub enum TypeError {
29    #[error("In instruction {instruction}: undefined memory reference {reference}.", instruction=.instruction.to_quil_or_debug())]
30    UndefinedMemoryReference {
31        instruction: Instruction,
32        reference: String,
33    },
34
35    #[error(
36        "In instruction {instruction}: data type mismatch; {dst} is of type {dst_type}, while {src} is of type {src_type}.", instruction=.instruction.to_quil_or_debug()
37    )]
38    DataTypeMismatch {
39        instruction: Instruction,
40        dst: String,
41        dst_type: String,
42        src: String,
43        src_type: String,
44    },
45
46    #[error(
47        "In instruction {instruction}: required a real value, but {value} has type {data_type}.", instruction=.instruction.to_quil_or_debug()
48    )]
49    RealValueRequired {
50        instruction: Instruction,
51        value: String,
52        data_type: String,
53    },
54
55    #[error(
56        "In instruction {instruction}: {operator} can only work with {correct_type} data, but operand {operand} has type {data_type}.", instruction=.instruction.to_quil_or_debug()
57    )]
58    OperatorOperandMismatch {
59        instruction: Instruction,
60        operator: String,
61        correct_type: String,
62        operand: String,
63        data_type: String,
64    },
65}
66
67pub type TypeResult<T> = Result<T, TypeError>;
68
69/// Check that the instructions of the given program obey the spec with regards to data types.
70///
71/// See the [Quil spec](https://quil-lang.github.io/).
72pub fn type_check(program: &Program) -> TypeResult<()> {
73    for instruction in &program.instructions {
74        match instruction {
75            Instruction::SetFrequency(SetFrequency { frequency, .. }) => {
76                should_be_real(instruction, frequency, &program.memory_regions)?
77            }
78            Instruction::SetPhase(SetPhase { phase, .. }) => {
79                should_be_real(instruction, phase, &program.memory_regions)?
80            }
81            Instruction::SetScale(SetScale { scale, .. }) => {
82                should_be_real(instruction, scale, &program.memory_regions)?
83            }
84            Instruction::ShiftFrequency(ShiftFrequency { frequency, .. }) => {
85                should_be_real(instruction, frequency, &program.memory_regions)?
86            }
87            Instruction::ShiftPhase(ShiftPhase { phase, .. }) => {
88                should_be_real(instruction, phase, &program.memory_regions)?
89            }
90            Instruction::Arithmetic(Arithmetic {
91                operator,
92                destination,
93                source,
94            }) => type_check_arithmetic(
95                instruction,
96                operator,
97                destination,
98                source,
99                &program.memory_regions,
100            )?,
101            Instruction::Comparison(Comparison {
102                operator,
103                destination,
104                lhs,
105                rhs,
106            }) => type_check_comparison(
107                instruction,
108                operator,
109                destination,
110                lhs,
111                rhs,
112                &program.memory_regions,
113            )?,
114            Instruction::BinaryLogic(BinaryLogic {
115                operator,
116                destination,
117                source,
118            }) => type_check_binary_logic(
119                instruction,
120                operator,
121                destination,
122                source,
123                &program.memory_regions,
124            )?,
125            Instruction::UnaryLogic(UnaryLogic { operator, operand }) => {
126                type_check_unary_logic(instruction, operator, operand, &program.memory_regions)?
127            }
128            Instruction::Move(Move {
129                destination,
130                source,
131            }) => type_check_move(instruction, destination, source, &program.memory_regions)?,
132            Instruction::Exchange(Exchange { left, right }) => {
133                type_check_exchange(instruction, left, right, &program.memory_regions)?
134            }
135            Instruction::Load(Load {
136                destination,
137                source,
138                offset,
139            }) => type_check_load(
140                instruction,
141                destination,
142                source,
143                offset,
144                &program.memory_regions,
145            )?,
146            Instruction::Store(Store {
147                destination,
148                offset,
149                source,
150            }) => type_check_store(
151                instruction,
152                destination,
153                offset,
154                source,
155                &program.memory_regions,
156            )?,
157            _ => {}
158        }
159    }
160    Ok(())
161}
162
163/// A convenient way to construct a TypeError.
164fn undefined_memory_reference(instruction: &Instruction, reference: impl Debug) -> TypeResult<()> {
165    Err(TypeError::UndefinedMemoryReference {
166        instruction: instruction.clone(),
167        reference: format!("{reference:#?}"),
168    })
169}
170
171/// A convenient way to construct a TypeError.
172fn data_type_mismatch(
173    instruction: &Instruction,
174    dst: impl Debug,
175    dst_type: impl Debug,
176    src: impl Debug,
177    src_type: impl Debug,
178) -> TypeResult<()> {
179    Err(TypeError::DataTypeMismatch {
180        instruction: instruction.clone(),
181        dst: format!("{dst:#?}"),
182        dst_type: format!("{dst_type:#?}"),
183        src: format!("{src:#?}"),
184        src_type: format!("{src_type:#?}"),
185    })
186}
187
188/// A convenient way to construct a TypeError.
189fn real_value_required(
190    instruction: &Instruction,
191    value: impl Debug,
192    data_type: impl Debug,
193) -> TypeResult<()> {
194    Err(TypeError::RealValueRequired {
195        instruction: instruction.clone(),
196        value: format!("{value:#?}"),
197        data_type: format!("#{data_type:#?}"),
198    })
199}
200
201/// A convenient way to construct a TypeError.
202fn operator_operand_mismatch(
203    instruction: &Instruction,
204    operator: impl Debug,
205    correct_type: impl Debug,
206    operand: impl Debug,
207    data_type: impl Debug,
208) -> TypeResult<()> {
209    Err(TypeError::OperatorOperandMismatch {
210        instruction: instruction.clone(),
211        operator: format!("{operator:#?}"),
212        correct_type: format!("{correct_type:#?}"),
213        operand: format!("{operand:#?}"),
214        data_type: format!("{data_type:#?}"),
215    })
216}
217
218/// In the [Instruction], the given [Expression] should be real-valued.
219fn should_be_real(
220    instruction: &Instruction,
221    this_expression: &Expression,
222    memory_regions: &IndexMap<String, MemoryRegion>,
223) -> TypeResult<()> {
224    match this_expression {
225        Expression::Address(reference) => {
226            if let Some(MemoryRegion { size, .. }) = memory_regions.get(&reference.name) {
227                let dt = &size.data_type;
228                if dt == &ScalarType::Real {
229                    Ok(())
230                } else {
231                    real_value_required(instruction, reference, dt)
232                }
233            } else {
234                undefined_memory_reference(instruction, reference)
235            }
236        }
237        Expression::FunctionCall(FunctionCallExpression { expression, .. }) => {
238            should_be_real(instruction, expression, memory_regions)
239        }
240        Expression::Infix(InfixExpression { left, right, .. }) => should_be_real(
241            instruction,
242            left,
243            memory_regions,
244        )
245        .and(should_be_real(instruction, right, memory_regions)),
246        Expression::Number(value) => {
247            if value.im.abs() > f64::EPSILON {
248                real_value_required(instruction, this_expression, "`imaginary`")
249            } else {
250                Ok(())
251            }
252        }
253        Expression::PiConstant() => Ok(()),
254        Expression::Prefix(PrefixExpression { expression, .. }) => {
255            should_be_real(instruction, expression, memory_regions)
256        }
257        Expression::Variable(_) => real_value_required(instruction, this_expression, "`variable`"),
258    }
259}
260
261/// Type check an [Instruction::Arithmetic].
262fn type_check_arithmetic(
263    instruction: &Instruction,
264    operator: &ArithmeticOperator,
265    destination: &MemoryReference,
266    source: &ArithmeticOperand,
267    memory_regions: &IndexMap<String, MemoryRegion>,
268) -> TypeResult<()> {
269    if let Some(dest_region) = memory_regions.get(&destination.name) {
270        let dt = &dest_region.size.data_type;
271        match (source, dt) {
272            (ArithmeticOperand::LiteralInteger(_), ScalarType::Integer) => Ok(()),
273            (ArithmeticOperand::LiteralReal(_), ScalarType::Real) => Ok(()),
274            (ArithmeticOperand::LiteralInteger(_), ScalarType::Real) => {
275                data_type_mismatch(instruction, destination, dt, source, "`literal integer`")
276            }
277            (ArithmeticOperand::LiteralReal(_), _) => {
278                data_type_mismatch(instruction, destination, dt, source, "`literal real`")
279            }
280            (_, ScalarType::Bit) | (_, ScalarType::Octet) => operator_operand_mismatch(
281                instruction,
282                operator,
283                "real or integral-valued",
284                destination,
285                dt,
286            ),
287            (ArithmeticOperand::MemoryReference(src_ref), _) => {
288                if let Some(src_region) = memory_regions.get(&src_ref.name) {
289                    let st = &src_region.size.data_type;
290                    match st {
291                        ScalarType::Bit | ScalarType::Octet => operator_operand_mismatch(
292                            instruction,
293                            operator,
294                            "real or integral-valued",
295                            src_ref,
296                            st,
297                        ),
298                        st if dt != st => {
299                            data_type_mismatch(instruction, destination, dt, src_ref, st)
300                        }
301                        _ => Ok(()),
302                    }
303                } else {
304                    undefined_memory_reference(instruction, src_ref)
305                }
306            }
307        }
308    } else {
309        undefined_memory_reference(instruction, destination)
310    }
311}
312
313/// Type check an [Instruction::Comparison].
314fn type_check_comparison(
315    instruction: &Instruction,
316    operator: &ComparisonOperator,
317    destination: &MemoryReference,
318    lhs: &MemoryReference,
319    rhs: &ComparisonOperand,
320    memory_regions: &IndexMap<String, MemoryRegion>,
321) -> TypeResult<()> {
322    match (
323        memory_regions.get(&destination.name),
324        memory_regions.get(&lhs.name),
325    ) {
326        (None, _) => undefined_memory_reference(instruction, destination),
327        (_, None) => undefined_memory_reference(instruction, lhs),
328        (Some(destination_region), Some(lhs_region)) => {
329            match destination_region.size.data_type {
330                ScalarType::Bit => (),
331                destination_ty @ (ScalarType::Integer | ScalarType::Octet | ScalarType::Real) => {
332                    operator_operand_mismatch(
333                        instruction,
334                        operator,
335                        "bit",
336                        destination,
337                        destination_ty,
338                    )?
339                }
340            };
341            let lhs_ty = &lhs_region.size.data_type;
342            match (lhs_ty, rhs) {
343                (ScalarType::Real, ComparisonOperand::LiteralInteger(_)) => {
344                    data_type_mismatch(instruction, lhs, lhs_ty, rhs, "`literal integer`")
345                }
346                (_, ComparisonOperand::LiteralReal(_)) if lhs_ty != &ScalarType::Real => {
347                    data_type_mismatch(instruction, lhs, lhs_ty, rhs, "`literal real`")
348                }
349                (_, ComparisonOperand::MemoryReference(rhs_ref)) => {
350                    if let Some(rhs_region) = memory_regions.get(&rhs_ref.name) {
351                        let rhs_ty = &rhs_region.size.data_type;
352                        if lhs_ty != rhs_ty {
353                            data_type_mismatch(instruction, lhs, lhs_ty, rhs, rhs_ty)
354                        } else {
355                            Ok(())
356                        }
357                    } else {
358                        undefined_memory_reference(instruction, rhs_ref)
359                    }
360                }
361                _ => Ok(()),
362            }
363        }
364    }
365}
366
367fn type_check_binary_logic_memory_reference(
368    instruction: &Instruction,
369    operator: &BinaryOperator,
370    reference: &MemoryReference,
371    memory_regions: &IndexMap<String, MemoryRegion>,
372) -> TypeResult<()> {
373    if let Some(region) = memory_regions.get(&reference.name) {
374        match &region.size.data_type {
375            ScalarType::Bit | ScalarType::Integer | ScalarType::Octet => Ok(()),
376            ty @ ScalarType::Real => {
377                operator_operand_mismatch(instruction, operator, "integral", reference, ty)
378            }
379        }
380    } else {
381        undefined_memory_reference(instruction, reference)
382    }
383}
384
385/// Type check an [Instruction::BinaryLogic].
386fn type_check_binary_logic(
387    instruction: &Instruction,
388    operator: &BinaryOperator,
389    destination: &MemoryReference,
390    source: &BinaryOperand,
391    memory_regions: &IndexMap<String, MemoryRegion>,
392) -> TypeResult<()> {
393    type_check_binary_logic_memory_reference(instruction, operator, destination, memory_regions)?;
394    match source {
395        BinaryOperand::LiteralInteger(_) => Ok(()),
396        BinaryOperand::MemoryReference(source_ref) => type_check_binary_logic_memory_reference(
397            instruction,
398            operator,
399            source_ref,
400            memory_regions,
401        ),
402    }
403}
404
405/// Type check an [Instruction::UnaryLogic].
406fn type_check_unary_logic(
407    instruction: &Instruction,
408    operator: &UnaryOperator,
409    operand: &MemoryReference,
410    memory_regions: &IndexMap<String, MemoryRegion>,
411) -> TypeResult<()> {
412    if let Some(MemoryRegion { size, .. }) = memory_regions.get(&operand.name) {
413        let dt = &size.data_type;
414        match (dt, operator) {
415            (ScalarType::Real, UnaryOperator::Not) => {
416                operator_operand_mismatch(instruction, operator, "integral", operand, dt)
417            }
418            (ScalarType::Bit, UnaryOperator::Neg) | (ScalarType::Octet, UnaryOperator::Neg) => {
419                operator_operand_mismatch(
420                    instruction,
421                    operator,
422                    "real or integral-valued",
423                    operand,
424                    dt,
425                )
426            }
427            (ScalarType::Real, UnaryOperator::Neg) | (ScalarType::Integer, UnaryOperator::Neg) => {
428                Ok(())
429            }
430            (ScalarType::Integer, UnaryOperator::Not)
431            | (ScalarType::Bit, UnaryOperator::Not)
432            | (ScalarType::Octet, UnaryOperator::Not) => Ok(()),
433        }
434    } else {
435        undefined_memory_reference(instruction, operand)
436    }
437}
438
439/// Type check an [Instruction::Move].
440fn type_check_move(
441    instruction: &Instruction,
442    destination: &MemoryReference,
443    source: &ArithmeticOperand,
444    memory_regions: &IndexMap<String, MemoryRegion>,
445) -> TypeResult<()> {
446    if let Some(dest_region) = memory_regions.get(&destination.name) {
447        let dt = &dest_region.size.data_type;
448        match (source, dt) {
449            (ArithmeticOperand::LiteralInteger(_), ScalarType::Real) => {
450                data_type_mismatch(instruction, destination, dt, source, "`literal integer`")
451            }
452            (ArithmeticOperand::LiteralReal(_), st) if st != &ScalarType::Real => {
453                data_type_mismatch(instruction, destination, dt, source, "`literal real`")
454            }
455            (ArithmeticOperand::MemoryReference(src_ref), dt) => {
456                if let Some(src_region) = memory_regions.get(&src_ref.name) {
457                    let st = &src_region.size.data_type;
458                    if st != dt {
459                        data_type_mismatch(instruction, destination, dt, source, st)
460                    } else {
461                        Ok(())
462                    }
463                } else {
464                    undefined_memory_reference(instruction, src_ref)
465                }
466            }
467            _ => Ok(()),
468        }
469    } else {
470        undefined_memory_reference(instruction, destination)
471    }
472}
473
474/// Type check an [Instruction::Exchange].
475fn type_check_exchange(
476    instruction: &Instruction,
477    left: &MemoryReference,
478    right: &MemoryReference,
479    memory_regions: &IndexMap<String, MemoryRegion>,
480) -> TypeResult<()> {
481    match (
482        memory_regions.get(&left.name),
483        memory_regions.get(&right.name),
484    ) {
485        (None, _) => undefined_memory_reference(instruction, left),
486        (_, None) => undefined_memory_reference(instruction, right),
487        (Some(left_region), Some(right_region)) => {
488            let (lt, rt) = (&left_region.size.data_type, &right_region.size.data_type);
489            if lt != rt {
490                data_type_mismatch(instruction, left, lt, right, rt)
491            } else {
492                Ok(())
493            }
494        }
495    }
496}
497
498/// Type check an [Instruction::Load].
499fn type_check_load(
500    instruction: &Instruction,
501    destination: &MemoryReference,
502    source: &str,
503    offset: &MemoryReference,
504    memory_regions: &IndexMap<String, MemoryRegion>,
505) -> TypeResult<()> {
506    match (
507        memory_regions.get(&destination.name),
508        memory_regions.get(source),
509        memory_regions.get(&offset.name),
510    ) {
511        (None, _, _) => undefined_memory_reference(instruction, destination),
512        (_, None, _) => undefined_memory_reference(instruction, source),
513        (_, _, None) => undefined_memory_reference(instruction, offset),
514        (Some(dest_region), Some(src_region), Some(offset_region)) => {
515            let (dt, st, ot) = (
516                &dest_region.size.data_type,
517                &src_region.size.data_type,
518                &offset_region.size.data_type,
519            );
520            if ot != &ScalarType::Integer {
521                operator_operand_mismatch(instruction, "LOAD", "integral", offset, ot)
522            } else if dt != st {
523                data_type_mismatch(instruction, destination, dt, source, st)
524            } else {
525                Ok(())
526            }
527        }
528    }
529}
530
531/// type check an [Instruction::Store].
532fn type_check_store(
533    instruction: &Instruction,
534    destination: &str,
535    offset: &MemoryReference,
536    source: &ArithmeticOperand,
537    memory_regions: &IndexMap<String, MemoryRegion>,
538) -> TypeResult<()> {
539    let dest_region =
540        memory_regions
541            .get(destination)
542            .ok_or(TypeError::UndefinedMemoryReference {
543                instruction: instruction.clone(),
544                reference: destination.to_string(),
545            })?;
546    memory_regions
547        .get(&offset.name)
548        .ok_or(TypeError::UndefinedMemoryReference {
549            instruction: instruction.clone(),
550            reference: offset.name.clone(),
551        })
552        .and_then(|m| {
553            if m.size.data_type != ScalarType::Integer {
554                operator_operand_mismatch(
555                    instruction,
556                    "STORE",
557                    "integral",
558                    offset,
559                    m.size.data_type,
560                )
561            } else {
562                Ok(())
563            }
564        })?;
565
566    match (source, &dest_region.size.data_type) {
567        (ArithmeticOperand::MemoryReference(source_ref), dt) => {
568            match memory_regions.get(&source_ref.name) {
569                None => undefined_memory_reference(instruction, offset),
570                Some(source_region) => {
571                    // https://quil-lang.github.io/#6-5Classical-Instructions
572                    // # Perform an indirect store of a to x offset by n.
573                    // STORE    x n a          # x[n] := a
574                    //      <oct*> <int> <oct>
575                    //      <oct*> <int> <!int>
576                    //      <int*> <int> <int>
577                    //      <int*> <int> <!int>
578                    //      <real*> <int> <real>
579                    //      <real*> <int> <!real>
580                    //      <bit*> <int> <bit>
581                    //      <bit*> <int> <!int>
582
583                    // <d*> <int> <s>  => check that d & s match
584                    let st = &source_region.size.data_type;
585                    if st == dt {
586                        Ok(())
587                    } else {
588                        data_type_mismatch(instruction, source_ref, st, destination, dt)
589                    }
590                }
591            }
592        }
593        (ArithmeticOperand::LiteralInteger(_), ScalarType::Octet) => Ok(()),
594        // <int*> <int> <!int>
595        (ArithmeticOperand::LiteralInteger(_), ScalarType::Integer) => Ok(()),
596        // <real*> <int> <!real>
597        (ArithmeticOperand::LiteralReal(_), ScalarType::Real) => Ok(()),
598        // <bit*> <int> <!bit>
599        (ArithmeticOperand::LiteralInteger(_), ScalarType::Bit) => Ok(()),
600        // Mismatches
601        (ArithmeticOperand::LiteralInteger(_), dt) => {
602            data_type_mismatch(instruction, source, "`literal integer`", destination, dt)
603        }
604        (ArithmeticOperand::LiteralReal(_), dt) => {
605            data_type_mismatch(instruction, source, "`literal real`", destination, dt)
606        }
607    }
608}
609
610#[cfg(test)]
611#[allow(clippy::too_many_arguments)]
612mod tests {
613    use super::*;
614    use crate::Program;
615    use rstest::*;
616    use std::str::FromStr;
617
618    #[rstest]
619    fn test_sets_and_shifts(
620        #[values("x", "y")] declared: &str,
621        #[values("REAL", "INTEGER", "BIT", "OCTET")] datatype: &str,
622        #[values(
623            "SET-FREQUENCY",
624            "SET-PHASE",
625            "SET-SCALE",
626            "SHIFT-FREQUENCY",
627            "SHIFT-PHASE"
628        )]
629        command: &str,
630        #[values("x", "y")] referenced: &str,
631    ) {
632        let p = Program::from_str(&format!(
633            r#"
634DECLARE {declared} {datatype}
635{command} 0 "xy" {referenced}
636"#
637        )).unwrap_or_else(|_| panic!("Bad program with (declared, datatype, command, referenced) = ({declared}, {datatype}, {command}, {referenced})."));
638        assert_eq!(
639            type_check(&p).is_ok(),
640            declared == referenced && datatype == "REAL"
641        );
642    }
643
644    #[rstest]
645    fn test_arithmetic_memory_references(
646        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
647        #[values("REAL", "INTEGER", "BIT", "OCTET")] src_type: &str,
648        #[values("ADD", "SUB", "MUL", "DIV")] command: &str,
649    ) {
650        let p = Program::from_str(&format!(
651            r#"
652DECLARE destination {dst_type}
653DECLARE source {src_type}
654{command} destination source
655"#
656        )).unwrap_or_else(|_| panic!("Bad arithmetic program with (command, dst_type, src_type) = ({command}, {dst_type}, {src_type})."));
657        assert_eq!(
658            type_check(&p).is_ok(),
659            dst_type == src_type && ["REAL", "INTEGER"].contains(&dst_type)
660        );
661    }
662
663    #[rstest]
664    fn test_arithmetic_immediate_values(
665        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
666        #[values("ADD", "SUB", "MUL", "DIV")] command: &str,
667        #[values("1", "1.0")] value: &str,
668    ) {
669        let p = Program::from_str(&format!(
670                r#"
671DECLARE destination {dst_type}
672{command} destination {value}
673"#
674        )).unwrap_or_else(|_| panic!("Bad ARITHMETIC program with (command, dst_type, value) = ({command}, {dst_type}, {value})."));
675        let (f, i) = (f64::from_str(value), i64::from_str(value));
676        assert_eq!(
677            type_check(&p).is_ok(),
678            (dst_type == "REAL" && f.is_ok() && i.is_err()) || (dst_type == "INTEGER" && i.is_ok())
679        );
680    }
681
682    #[rstest]
683    fn test_comparison_memory_references(
684        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
685        #[values("REAL", "INTEGER", "BIT", "OCTET")] left_type: &str,
686        #[values("REAL", "INTEGER", "BIT", "OCTET")] right_type: &str,
687        #[values("EQ", "GT", "GE", "LT", "LE")] comparison: &str,
688    ) {
689        let p = Program::from_str(&format!(
690                                r#"
691DECLARE destination {dst_type}
692DECLARE left {left_type}
693DECLARE right {right_type}
694{comparison} destination left right
695"#
696        )).unwrap_or_else(|_| panic!("Bad comparison program with (dst_type, left_type, right_type, comparison) = ({dst_type}, {left_type}, {right_type}, {comparison})."));
697        assert_eq!(
698            type_check(&p).is_ok(),
699            dst_type == "BIT" && left_type == right_type
700        );
701    }
702
703    #[rstest]
704    fn test_comparison_immediate_values(
705        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
706        #[values("REAL", "INTEGER", "BIT", "OCTET")] left_type: &str,
707        #[values("EQ", "GT", "GE", "LT", "LE")] comparison: &str,
708        #[values("1.0", "1")] value: &str,
709    ) {
710        let p = Program::from_str(&format!(
711                                r#"
712DECLARE destination {dst_type}
713DECLARE left {left_type}
714{comparison} destination left {value}
715"#
716        )).unwrap_or_else(|_| panic!("Bad comparison program with (dst_type, left_type, comparison, value) = ({dst_type}, {left_type}, {comparison}, {value})."));
717        let (f, i) = (f64::from_str(value), i64::from_str(value));
718        assert_eq!(
719            type_check(&p).is_ok(),
720            dst_type == "BIT"
721                && ((left_type == "REAL" && f.is_ok() && i.is_err())
722                    || (left_type != "REAL" && i.is_ok()))
723        );
724    }
725
726    #[rstest]
727    fn test_binary_logic_memory_references(
728        #[values("x")] dst_decl: &str,
729        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
730        #[values("y")] src_decl: &str,
731        #[values("REAL", "INTEGER", "BIT", "OCTET")] src_type: &str,
732        #[values("AND", "IOR", "XOR")] operator: &str,
733        #[values("x", "not_x")] dst_ref: &str,
734        #[values("y", "not_y")] src_ref: &str,
735    ) {
736        let p = Program::from_str(&format!(
737                r#"
738DECLARE {dst_decl} {dst_type}
739DECLARE {src_decl} {src_type}
740{operator} {dst_ref} {src_ref}
741"#
742        )).unwrap_or_else(|_| panic!("Bad bianry logic program with (dst_decl, dst_type, src_decl, src_type, operator, dst_ref, src_ref) = ({dst_decl}, {dst_type}, {src_decl}, {src_type}, {operator}, {dst_ref}, {src_ref})."));
743        assert_eq!(
744            type_check(&p).is_ok(),
745            dst_type != "REAL" && src_type != "REAL" && dst_decl == dst_ref && src_decl == src_ref
746        );
747    }
748
749    #[rstest]
750    fn test_binary_logic_immediate_values(
751        #[values("x")] dst_decl: &str,
752        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
753        #[values("AND", "IOR", "XOR")] operator: &str,
754        #[values("x", "not_x")] dst_ref: &str,
755    ) {
756        let p = Program::from_str(&format!(
757                r#"
758DECLARE {dst_decl} {dst_type}
759{operator} {dst_ref} 1
760"#
761            )).unwrap_or_else(|_| panic!("Bad binary logic program with (dst_decl, dst_type, operator, dst_ref) = ({dst_decl}, {dst_type}, {operator}, {dst_ref})."));
762        assert_eq!(
763            type_check(&p).is_ok(),
764            dst_type != "REAL" && dst_ref == dst_decl
765        );
766    }
767
768    #[rstest]
769    fn test_unary_logic(
770        #[values("x")] destination: &str,
771        #[values("REAL", "INTEGER", "BIT", "OCTET")] datatype: &str,
772        #[values("NEG", "NOT")] operator: &str,
773        #[values("x", "not_x")] reference: &str,
774    ) {
775        let p = Program::from_str(&format!(
776                r#"
777DECLARE {destination} {datatype}
778{operator} {reference}
779"#
780        )).unwrap_or_else(|_| panic!("Bad unary program with (destination, datatype, operator, reference) = ({destination}, {datatype}, {operator}, {reference}"));
781        assert_eq!(
782            type_check(&p).is_ok(),
783            destination == reference
784                && ((["REAL", "INTEGER"].contains(&datatype) && operator == "NEG")
785                    || (["INTEGER", "BIT", "OCTET"].contains(&datatype) && operator == "NOT"))
786        );
787    }
788
789    #[rstest]
790    fn test_move_memory_references(
791        #[values("x")] dst_decl: &str,
792        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
793        #[values("y")] src_decl: &str,
794        #[values("REAL", "INTEGER", "BIT", "OCTET")] src_type: &str,
795        #[values("x", "not_x")] dst_ref: &str,
796        #[values("y", "not_y")] src_ref: &str,
797    ) {
798        let p = Program::from_str(&format!(
799            r#"
800DECLARE {dst_decl} {dst_type}
801DECLARE {src_decl} {src_type}
802MOVE {dst_ref} {src_ref}
803"#
804        )).unwrap_or_else(|_| panic!("Bad MOVE program with (dst_decl, dst_type, src_decl, src_type, dst_ref, src_ref) = ({dst_decl}, {dst_type}, {src_decl}, {src_type}, {dst_ref}, {src_ref})."));
805        assert_eq!(
806            type_check(&p).is_ok(),
807            dst_type == src_type && dst_decl == dst_ref && src_decl == src_ref
808        );
809    }
810
811    #[rstest]
812    fn test_move_immediate_values(
813        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
814        #[values("1", "1.0")] value: &str,
815    ) {
816        let p = Program::from_str(&format!(
817            r#"
818DECLARE destination {dst_type}
819MOVE destination {value}
820"#
821        ))
822        .unwrap_or_else(|_| {
823            panic!("Bad MOVE program with (dst_type, source) = ({dst_type}, {value}).")
824        });
825        let (f, i) = (f64::from_str(value), i64::from_str(value));
826        assert_eq!(
827            type_check(&p).is_ok(),
828            (dst_type == "REAL" && f.is_ok() && i.is_err()) || (dst_type != "REAL" && i.is_ok())
829        );
830    }
831
832    #[rstest]
833    fn test_exchange(
834        #[values("x")] left_decl: &str,
835        #[values("REAL", "INTEGER", "BIT", "OCTET")] left_type: &str,
836        #[values("y")] right_decl: &str,
837        #[values("REAL", "INTEGER", "BIT", "OCTET")] right_type: &str,
838        #[values("x", "not_x")] left_ref: &str,
839        #[values("y", "not_y")] right_ref: &str,
840    ) {
841        let p = Program::from_str(&format!(
842                r#"
843DECLARE {left_decl} {left_type}
844DECLARE {right_decl} {right_type}
845EXCHANGE {left_ref} {right_ref}
846"#
847        )).unwrap_or_else(|_| panic!("Bad EXCHANGE program with (left_decl, left_type, right_decl, right_type, left_ref, right_ref) = ({left_decl}, {left_type}, {right_decl}, {right_type}, {left_ref}, {right_ref})."));
848        assert_eq!(
849            type_check(&p).is_ok(),
850            left_decl == left_ref && right_decl == right_ref && left_type == right_type
851        );
852    }
853
854    #[rstest]
855    fn test_load(
856        #[values("x")] dst_decl: &str,
857        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
858        #[values("y")] src_decl: &str,
859        #[values("REAL", "INTEGER", "BIT", "OCTET")] src_type: &str,
860        #[values("z")] offset_decl: &str,
861        #[values("REAL", "INTEGER", "BIT", "OCTET")] offset_type: &str,
862        #[values("x", "not_z")] dst_ref: &str,
863        #[values("y", "not_y")] src_ref: &str,
864        #[values("z", "not_z")] offset_ref: &str,
865    ) {
866        let p = Program::from_str(&format!(
867                r#"
868DECLARE {dst_decl} {dst_type}
869DECLARE {src_decl} {src_type}
870DECLARE {offset_decl} {offset_type}
871LOAD {dst_ref} {src_ref} {offset_ref}
872"#
873        )).unwrap_or_else(|_| panic!("Bad LOAD program with (dst_decl, dst_type, src_decl, src_type, offset_decl, offset_type, dst_ref, src_ref, offset_ref) = ({dst_decl}, {dst_type}, {src_decl}, {src_type}, {offset_decl}, {offset_type}, {dst_ref}, {src_ref}, {offset_ref})."));
874        assert_eq!(
875            type_check(&p).is_ok(),
876            (dst_decl == dst_ref && src_decl == src_ref && offset_decl == offset_ref)
877                && (dst_type == src_type)
878                && (offset_type == "INTEGER")
879        );
880    }
881
882    #[rstest]
883    fn test_store_memory_references(
884        #[values("x")] dst_decl: &str,
885        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
886        #[values("y")] src_decl: &str,
887        #[values("REAL", "INTEGER", "BIT", "OCTET")] src_type: &str,
888        #[values("z")] offset_decl: &str,
889        #[values("REAL", "INTEGER", "BIT", "OCTET")] offset_type: &str,
890        #[values("x", "not_z")] dst_ref: &str,
891        #[values("y", "not_y")] src_ref: &str,
892        #[values("z", "not_z")] offset_ref: &str,
893    ) {
894        let p = Program::from_str(&format!(
895                r#"
896DECLARE {dst_decl} {dst_type}
897DECLARE {src_decl} {src_type}
898DECLARE {offset_decl} {offset_type}
899STORE {dst_ref} {offset_ref} {src_ref}
900"#
901        )).unwrap_or_else(|_| panic!("Bad STORE program with (dst_decl, dst_type, src_decl, src_type, offset_decl, offset_type, dst_ref, src_ref, offset_ref) = ({dst_decl}, {dst_type}, {src_decl}, {src_type}, {offset_decl}, {offset_type}, {dst_ref}, {src_ref}, {offset_ref})."));
902        assert_eq!(
903            type_check(&p).is_ok(),
904            (dst_decl == dst_ref && src_decl == src_ref && offset_decl == offset_ref)
905                && (dst_type == src_type)
906                && (offset_type == "INTEGER")
907        );
908    }
909
910    #[rstest]
911    fn test_store_immediate_values(
912        #[values("x")] dst_decl: &str,
913        #[values("REAL", "INTEGER", "BIT", "OCTET")] dst_type: &str,
914        #[values("1.0", "1")] value: &str,
915        #[values("z")] offset_decl: &str,
916        #[values("REAL", "INTEGER", "BIT", "OCTET")] offset_type: &str,
917        #[values("x", "not_z")] dst_ref: &str,
918        #[values("z", "not_z")] offset_ref: &str,
919    ) {
920        let p = Program::from_str(&format!(
921                r#"
922DECLARE {dst_decl} {dst_type}
923DECLARE {offset_decl} {offset_type}
924STORE {dst_ref} {offset_ref} {value}
925"#
926        )).unwrap_or_else(|e| panic!("Bad STORE program with (dst_decl, dst_type, value, offset_decl, offset_type, dst_ref, offset_ref) = ({dst_decl}, {dst_type}, {value}, {offset_decl}, {offset_type}, {dst_ref}, {offset_ref}).: {e}"));
927        let (f, i) = (f64::from_str(value), i64::from_str(value));
928        assert_eq!(
929            type_check(&p).is_ok(),
930            (dst_decl == dst_ref && offset_decl == offset_ref)
931                && ((dst_type == "REAL" && f.is_ok() && i.is_err())
932                    || (dst_type != "REAL" && i.is_ok()))
933                && (offset_type == "INTEGER")
934        );
935    }
936}