1#![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#[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
69pub 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
163fn undefined_memory_reference(instruction: &Instruction, reference: impl Debug) -> TypeResult<()> {
165 Err(TypeError::UndefinedMemoryReference {
166 instruction: instruction.clone(),
167 reference: format!("{reference:#?}"),
168 })
169}
170
171fn 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
188fn 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
201fn 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
218fn 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
261fn 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
313fn 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 ®ion.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
385fn 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
405fn 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
439fn 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
474fn 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
498fn 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
531fn 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 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 (ArithmeticOperand::LiteralInteger(_), ScalarType::Integer) => Ok(()),
596 (ArithmeticOperand::LiteralReal(_), ScalarType::Real) => Ok(()),
598 (ArithmeticOperand::LiteralInteger(_), ScalarType::Bit) => Ok(()),
600 (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}