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