From 2f2317c16af9ee66eba08b0d92d4456cdc443ffc Mon Sep 17 00:00:00 2001 From: Jacob Johannsen Date: Tue, 24 Oct 2023 22:12:34 +0200 Subject: [PATCH] Simple test added --- Cargo.lock | 1 + hir/Cargo.toml | 1 + hir/src/parser/ast/block.rs | 11 +- hir/src/parser/ast/functions.rs | 22 +- hir/src/parser/ast/globals.rs | 10 +- hir/src/parser/ast/instruction.rs | 23 +- hir/src/parser/ast/mod.rs | 13 +- hir/src/parser/parser/mod.rs | 3 + hir/src/parser/parser/tests/input/system.mir | 12 + hir/src/parser/parser/tests/mod.rs | 753 +++++++++++++++++++ hir/src/parser/parser/tests/utils.rs | 156 ++++ 11 files changed, 998 insertions(+), 7 deletions(-) create mode 100644 hir/src/parser/parser/tests/input/system.mir create mode 100644 hir/src/parser/parser/tests/mod.rs create mode 100644 hir/src/parser/parser/tests/utils.rs diff --git a/Cargo.lock b/Cargo.lock index f2fd5c47..3a426243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -906,6 +906,7 @@ dependencies = [ "miden-parsing", "paste", "petgraph", + "pretty_assertions", "rustc-hash", "smallvec", "thiserror", diff --git a/hir/Cargo.toml b/hir/Cargo.toml index 3e11ff6c..2ecaefa8 100644 --- a/hir/Cargo.toml +++ b/hir/Cargo.toml @@ -25,6 +25,7 @@ miden-hir-type.workspace = true miden-parsing = "0.1" petgraph = "0.6" paste.workspace = true +pretty_assertions = "1.0" rustc-hash.workspace = true smallvec.workspace = true thiserror.workspace = true diff --git a/hir/src/parser/ast/block.rs b/hir/src/parser/ast/block.rs index 9e33b7c6..e15ec137 100644 --- a/hir/src/parser/ast/block.rs +++ b/hir/src/parser/ast/block.rs @@ -6,6 +6,7 @@ const INDENT: &str = " "; /// Represents the label at the start of a basic block. /// /// Labels must be unique within each function. +#[derive(PartialEq, Debug)] pub struct Label { pub name: Ident, } @@ -21,6 +22,7 @@ impl fmt::Display for Label { } /// Represents an argument for a basic block +#[derive(PartialEq, Debug)] pub struct BlockArgument { pub value: Value, pub ty: Type, @@ -37,6 +39,7 @@ impl fmt::Display for BlockArgument { } /// Represents the label and the arguments of a basic block +#[derive(PartialEq, Debug)] pub struct BlockHeader { pub label: Label, pub args: Vec, @@ -66,7 +69,7 @@ impl fmt::Display for BlockHeader { } /// Represents a basic block of instructions -#[derive(Spanned)] +#[derive(Spanned, Debug)] pub struct Block { #[span] pub span: SourceSpan, @@ -82,6 +85,12 @@ impl Block { } } } +impl PartialEq for Block { + fn eq(&self, other: &Self) -> bool { + self.header == other.header + && self.instructions == other.instructions + } +} impl fmt::Display for Block { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "{}", self.header)?; diff --git a/hir/src/parser/ast/functions.rs b/hir/src/parser/ast/functions.rs index 332c83df..83a42101 100644 --- a/hir/src/parser/ast/functions.rs +++ b/hir/src/parser/ast/functions.rs @@ -2,6 +2,7 @@ use super::*; use crate::{ArgumentExtension, ArgumentPurpose, CallConv, FunctionIdent, Type}; /// The possible visibilities of a function +#[derive(PartialEq, Debug)] pub enum Visibility { /// (Module) private visibility Private, @@ -11,6 +12,7 @@ pub enum Visibility { /// A single parameter to a function. /// Parameter names are defined in the entry block for the function. +#[derive(PartialEq, Debug)] pub struct FunctionParameter { /// The purpose of the parameter (default or struct return) pub purpose: ArgumentPurpose, @@ -44,6 +46,7 @@ impl fmt::Display for FunctionParameter { } /// A single return value from a function. +#[derive(PartialEq, Debug)] pub struct FunctionReturn { /// The bit extension for the parameter pub extension: ArgumentExtension, @@ -67,7 +70,7 @@ impl fmt::Display for FunctionReturn { } /// Represents the type signature of a function -#[derive(Spanned)] +#[derive(Spanned, Debug)] pub struct FunctionSignature { #[span] pub span: SourceSpan, @@ -96,6 +99,15 @@ impl FunctionSignature { } } } +impl PartialEq for FunctionSignature { + fn eq(&self, other: &Self) -> bool { + self.visibility == other.visibility + && self.call_convention == other.call_convention + && self.name == other.name + && self.params == other.params + && self.returns == other.returns + } +} impl fmt::Display for FunctionSignature { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.visibility { @@ -128,7 +140,7 @@ impl fmt::Display for FunctionSignature { } /// Represents the declaration of a function -#[derive(Spanned)] +#[derive(Spanned, Debug)] pub struct FunctionDeclaration { #[span] pub span: SourceSpan, @@ -144,6 +156,12 @@ impl FunctionDeclaration { } } } +impl PartialEq for FunctionDeclaration { + fn eq(&self, other: &Self) -> bool { + self.signature == other.signature + && self.blocks == other.blocks + } +} impl fmt::Display for FunctionDeclaration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.signature)?; diff --git a/hir/src/parser/ast/globals.rs b/hir/src/parser/ast/globals.rs index 42ec9f84..d048b30c 100644 --- a/hir/src/parser/ast/globals.rs +++ b/hir/src/parser/ast/globals.rs @@ -34,7 +34,7 @@ impl fmt::Display for GlobalVarInitializer { } /// This represents the declaration of a Miden IR global variable -#[derive(Spanned)] +#[derive(Spanned, Debug)] pub struct GlobalVarDeclaration { #[span] pub span: SourceSpan, @@ -60,6 +60,14 @@ impl GlobalVarDeclaration { self.init = Some(init) } } +impl PartialEq for GlobalVarDeclaration { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.ty == other.ty + && self.linkage == other.linkage + && self.init == other.init + } +} impl fmt::Display for GlobalVarDeclaration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {} {}", self.name, self.ty, self.linkage)?; diff --git a/hir/src/parser/ast/instruction.rs b/hir/src/parser/ast/instruction.rs index 034d2c8f..b23ee8af 100644 --- a/hir/src/parser/ast/instruction.rs +++ b/hir/src/parser/ast/instruction.rs @@ -5,6 +5,7 @@ use crate::{FunctionIdent, Ident, Overflow, Type}; /// /// All intermediate values are named, and have an associated [Value]. /// Value identifiers must be globally unique. +#[derive(PartialEq, Debug)] pub struct Value { pub name: Ident, } @@ -20,6 +21,7 @@ impl fmt::Display for Value { } /// Immediates are converted at a later stage +#[derive(PartialEq, Debug)] pub enum Immediate { Pos(u128), Neg(u128), @@ -39,7 +41,7 @@ impl fmt::Display for Immediate { /// An instruction consists of a single operation, and a number of values that /// represent the results of the operation. Additionally, the instruction contains /// the types of the produced results -#[derive(Spanned)] +#[derive(Spanned, Debug)] pub struct Instruction { #[span] pub span: SourceSpan, @@ -57,6 +59,13 @@ impl Instruction { } } } +impl PartialEq for Instruction { + fn eq(&self, other: &Self) -> bool { + self.values == other.values + && self.op == other.op + && self.types == other.types + } +} impl fmt::Display for Instruction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.values.is_empty() { @@ -83,6 +92,7 @@ impl fmt::Display for Instruction { } /// Represents a operation and its arguments +#[derive(PartialEq, Debug)] pub enum Operation { BinaryOp(BinaryOpCode, Value, Value), BinaryImmOp(BinaryImmOpCode, Value, Immediate), @@ -179,6 +189,7 @@ impl fmt::Display for Operation { } /// Used to distinguish between user calls and kernel calls +#[derive(PartialEq, Debug)] pub enum CallOp { Call, SysCall, @@ -193,6 +204,7 @@ impl fmt::Display for CallOp { } /// Used to distinguish between binary operations +#[derive(PartialEq, Debug)] pub enum BinaryOpCode { Add(Overflow), Sub(Overflow), @@ -287,6 +299,7 @@ impl fmt::Display for BinaryOpCode { } /// Used to distinguish between immediate binary operations +#[derive(PartialEq, Debug)] pub enum BinaryImmOpCode { AddImm(Overflow), SubImm(Overflow), @@ -367,6 +380,7 @@ impl fmt::Display for BinaryImmOpCode { } /// Used to distinguish between unary operations +#[derive(PartialEq, Debug)] pub enum UnaryOpCode { Inv, Incr, @@ -405,6 +419,7 @@ impl fmt::Display for UnaryOpCode { } /// Used to distinguish between immediate unary operations +#[derive(PartialEq, Debug)] pub enum UnaryImmOpCode { I1, I8, @@ -430,6 +445,7 @@ impl fmt::Display for UnaryImmOpCode { } /// Used to distinguish between primary operations +#[derive(PartialEq, Debug)] pub enum PrimOpCode { Select, Assert, @@ -453,6 +469,7 @@ impl fmt::Display for PrimOpCode { /// Memory offset for global variable reads. /// Conversion to i32 happens during transformation to hir. +#[derive(PartialEq, Debug)] pub enum Offset { Pos(u128), Neg(u128), @@ -483,6 +500,7 @@ impl fmt::Display for Offset { } /// Used to distinguish between nested global value operations +#[derive(PartialEq, Debug)] pub enum GlobalValueOpNested { Symbol(Ident, Offset), Load(Box, Offset), @@ -511,6 +529,7 @@ impl fmt::Display for GlobalValueOpNested { } /// Used to distinguish between top-level global value operations +#[derive(PartialEq, Debug)] pub enum GlobalValueOp { Symbol(Ident, Offset), Load(GlobalValueOpNested, Offset), @@ -544,6 +563,7 @@ impl fmt::Display for GlobalValueOp { } /// The destination of a branch/jump +#[derive(PartialEq, Debug)] pub struct Destination { pub label: Label, pub args: Vec, @@ -572,6 +592,7 @@ impl fmt::Display for Destination { } /// A branch of a switch operation +#[derive(PartialEq, Debug)] pub enum SwitchBranch { Test(u128, Label), Default(Label), diff --git a/hir/src/parser/ast/mod.rs b/hir/src/parser/ast/mod.rs index cdf6a0bc..6aa1a6c5 100644 --- a/hir/src/parser/ast/mod.rs +++ b/hir/src/parser/ast/mod.rs @@ -17,7 +17,7 @@ use crate::Ident; /// This is a type alias used to clarify that an identifier refers to a module pub type ModuleId = Ident; -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ModuleType { /// Kernel context module Kernel, @@ -35,7 +35,7 @@ impl fmt::Display for ModuleType { /// This represents the parsed contents of a single Miden IR module /// -#[derive(Spanned)] +#[derive(Spanned, Debug)] pub struct Module { #[span] pub span: SourceSpan, @@ -66,6 +66,15 @@ impl Module { } } } +impl PartialEq for Module { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.ty == other.ty + && self.global_vars == other.global_vars + && self.functions == other.functions + && self.externals == other.externals + } +} impl fmt::Display for Module { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "{} {}", self.ty, self.name)?; diff --git a/hir/src/parser/parser/mod.rs b/hir/src/parser/parser/mod.rs index c645f33a..24b5514f 100644 --- a/hir/src/parser/parser/mod.rs +++ b/hir/src/parser/parser/mod.rs @@ -212,3 +212,6 @@ impl miden_parsing::Parse for ast::Module { } } } + +#[cfg(test)] +mod tests; diff --git a/hir/src/parser/parser/tests/input/system.mir b/hir/src/parser/parser/tests/input/system.mir new file mode 100644 index 00000000..797669a8 --- /dev/null +++ b/hir/src/parser/parser/tests/input/system.mir @@ -0,0 +1,12 @@ +module miden_ir_test + +global_1 u32 internal = 0xCAFEBABE + +pub cc(fast) fn miden_ir_test::test_func (zext u32, sext u32) -> u32 { + blk(v1 : u32, v2 : u32) : { + v3 = add.unchecked v1 v2 : u32 + ret (v1, v3) + } +} + +cc(kernel) fn exported::f1 (sret { u32, u32 }) -> [i8 ; 42]; diff --git a/hir/src/parser/parser/tests/mod.rs b/hir/src/parser/parser/tests/mod.rs new file mode 100644 index 00000000..7263d41c --- /dev/null +++ b/hir/src/parser/parser/tests/mod.rs @@ -0,0 +1,753 @@ +use crate::parser::ast::*; +use crate::{ArgumentExtension, ArgumentPurpose, CallConv, FunctionIdent, Ident, Linkage, Overflow, StructType, Type}; + +//macro_rules! assert_matches { +// ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => { +// match $left { +// $( $pattern )|+ $( if $guard )? => {} +// ref left_val => { +// panic!(r#"assertion failed: `(left matches right)` +// left: `{:?}`, +// right: `{:?}`"#, +// left_val, stringify!($($pattern)|+ $(if $guard)?)); +// } +// } +// } +//} + +mod utils; +use self::utils::ParseTest; + +//macro_rules! assert_module_error { +// ($source:expr, $pattern:pat_param) => { +// if let Err(err) = ParseTest::new().parse_module($source) { +// assert_matches!(err, $pattern) +// } else { +// panic!("expected parsing to fail, but it succeeded"); +// } +// }; +//} + +macro_rules! ident { + ($name:ident) => { + Ident::new( + crate::Symbol::intern(stringify!($name)), + miden_diagnostics::SourceSpan::UNKNOWN, + ) + }; +// +// ($name:literal) => { +// Identifier::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// crate::Symbol::intern($name), +// ) +// }; +// +// ($module:ident, $name:ident) => { +// QualifiedIdentifier::new( +// ident!($module), +// NamespacedIdentifier::Binding(ident!($name)), +// ) +// }; +} + +macro_rules! function_ident { + ($module:ident, $name:ident) => { + FunctionIdent { + module: ident!($module), + function: ident!($name), + } + }; +} + +// +//#[allow(unused)] +//macro_rules! global { +// ($name:ident, $offset:literal, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Global(ident!($name)), +// access_type: AccessType::Default, +// offset: $offset, +// ty: Some($ty), +// }) +// }; +// +// ($name:ident, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Global(ident!($name)), +// access_type: AccessType::Default, +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($name:literal, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Global(ident!($name)), +// access_type: AccessType::Default, +// offset: 0, +// ty: Some($ty), +// }) +// }; +//} +// +//macro_rules! access { +// ($name:ident) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Default, +// 0, +// )) +// }; +// +// ($name:literal) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Default, +// 0, +// )) +// }; +// +// ($module:ident, $name:ident, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Resolved(ident!($module, $name)), +// access_type: AccessType::Default, +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($name:ident, $offset:literal) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Default, +// $offset, +// )) +// }; +// +// ($name:literal, $offset:literal) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Default, +// $offset, +// )) +// }; +// +// ($name:ident, $offset:literal, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Default, +// offset: $offset, +// ty: Some($ty), +// }) +// }; +// +// ($name:ident, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Default, +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($name:literal, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Default, +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($name:ident [ $idx:literal ]) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Index($idx), +// 0, +// )) +// }; +// +// ($name:literal [ $idx:literal ]) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Index($idx), +// 0, +// )) +// }; +// +// ($name:ident [ $row:literal ] [ $col:literal ]) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Matrix($row, $col), +// 0, +// )) +// }; +// +// ($name:ident [ $row:literal ] [ $col:literal ], $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Matrix($row, $col), +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($module:ident, $name:ident [ $idx:literal ], $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ident!($module, $name).into(), +// access_type: AccessType::Index($idx), +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($module:ident, $name:ident [ $row:literal ] [ $col:literal ], $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ident!($module, $name).into(), +// access_type: AccessType::Matrix($row, $col), +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($name:ident [ $idx:literal ], $offset:literal) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Index($idx), +// $offset, +// )) +// }; +// +// ($name:ident [ $idx:literal ], $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Index($idx), +// offset: 0, +// ty: Some($ty), +// }) +// }; +// +// ($name:ident [ $idx:literal ], $offset:literal, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Index($idx), +// offset: $offset, +// ty: Some($ty), +// }) +// }; +// +// ($name:literal [ $idx:literal ], $offset:literal) => { +// ScalarExpr::SymbolAccess(SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Index($idx), +// $offset, +// )) +// }; +//} +// +//macro_rules! expr { +// ($expr:expr) => { +// Expr::try_from($expr).unwrap() +// }; +//} +// +//macro_rules! slice { +// ($name:ident, $range:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Unresolved(NamespacedIdentifier::Binding(ident!($name))), +// access_type: AccessType::Slice($range), +// offset: 0, +// ty: None, +// }) +// }; +// +// ($name:ident, $range:expr, $ty:expr) => { +// ScalarExpr::SymbolAccess(SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Slice($range), +// offset: 0, +// ty: Some($ty), +// }) +// }; +//} +// +//macro_rules! bounded_access { +// ($name:ident, $bound:expr) => { +// ScalarExpr::BoundedSymbolAccess(BoundedSymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Default, +// 0, +// ), +// $bound, +// )) +// }; +// +// ($name:ident, $bound:expr, $ty:expr) => { +// ScalarExpr::BoundedSymbolAccess(BoundedSymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Default, +// offset: 0, +// ty: Some($ty), +// }, +// $bound, +// )) +// }; +// +// ($name:ident [ $idx:literal ], $bound:expr) => { +// ScalarExpr::BoundedSymbolAccess(BoundedSymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// SymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// ident!($name), +// AccessType::Index($idx), +// 0, +// ), +// $bound, +// )) +// }; +// +// ($name:ident [ $idx:literal ], $bound:expr, $ty:expr) => { +// ScalarExpr::BoundedSymbolAccess(BoundedSymbolAccess::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// SymbolAccess { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// name: ResolvableIdentifier::Local(ident!($name)), +// access_type: AccessType::Index($idx), +// offset: 0, +// ty: Some($ty), +// }, +// $bound, +// )) +// }; +//} +// +//macro_rules! int { +// ($value:literal) => { +// ScalarExpr::Const(miden_diagnostics::Span::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// $value, +// )) +// }; +// +// ($value:expr) => { +// ScalarExpr::Const(miden_diagnostics::Span::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// $value, +// )) +// }; +//} +// +//macro_rules! call { +// ($callee:ident ($($param:expr),+)) => { +// ScalarExpr::Call(Call::new(miden_diagnostics::SourceSpan::UNKNOWN, ident!($callee), vec![$($param),+])) +// }; +// +// ($module:ident :: $callee:ident ($($param:expr),+)) => { +// ScalarExpr::Call(Call { +// span: miden_diagnostics::SourceSpan::UNKNOWN, +// callee: ResolvableIdentifier::Resolved(function_ident!($module, $callee)), +// args: vec![$($param),+], +// ty: None, +// }) +// } +//} +// +//macro_rules! trace_segment { +// ($idx:literal, $name:literal, [$(($binding_name:ident, $binding_size:literal)),*]) => { +// TraceSegment::new(miden_diagnostics::SourceSpan::UNKNOWN, $idx, ident!($name), vec![ +// $(miden_diagnostics::Span::new(miden_diagnostics::SourceSpan::UNKNOWN, (ident!($binding_name), $binding_size))),* +// ]) +// } +//} +// +//macro_rules! random_values { +// ($name:literal, $size:literal) => { +// RandomValues::with_size(miden_diagnostics::SourceSpan::UNKNOWN, ident!($name), $size) +// }; +// +// ($name:literal, [$(($binding_name:ident, $binding_size:literal)),*]) => { +// RandomValues::new(miden_diagnostics::SourceSpan::UNKNOWN, ident!($name), vec![ +// $(miden_diagnostics::Span::new(miden_diagnostics::SourceSpan::UNKNOWN, (ident!($binding_name), $binding_size))),* +// ]) +// } +//} +// +//macro_rules! constant { +// ($name:ident = $value:literal) => { +// Constant::new( +// SourceSpan::UNKNOWN, +// ident!($name), +// ConstantExpr::Scalar($value), +// ) +// }; +// +// ($name:ident = [$($value:literal),+]) => { +// Constant::new(SourceSpan::UNKNOWN, ident!($name), ConstantExpr::Vector(vec![$($value),+])) +// }; +// +// ($name:ident = [$([$($value:literal),+]),+]) => { +// Constant::new(SourceSpan::UNKNOWN, ident!($name), ConstantExpr::Matrix(vec![$(vec![$($value),+]),+])) +// }; +//} +// +//macro_rules! vector { +// ($($value:literal),*) => { +// Expr::Const(miden_diagnostics::Span::new(miden_diagnostics::SourceSpan::UNKNOWN, ConstantExpr::Vector(vec![$($value),*]))) +// }; +// +// ($($value:expr),*) => { +// Expr::Vector(miden_diagnostics::Span::new(miden_diagnostics::SourceSpan::UNKNOWN, vec![$(expr!($value)),*])) +// } +//} +// +//macro_rules! matrix { +// ($([$($value:expr),+]),+) => { +// Expr::Matrix(miden_diagnostics::Span::new(miden_diagnostics::SourceSpan::UNKNOWN, vec![$(vec![$($value),+]),+])) +// }; +//} +// +//macro_rules! let_ { +// ($name:ident = $value:expr => $($body:expr),+) => { +// Statement::Let(Let::new(miden_diagnostics::SourceSpan::UNKNOWN, ident!($name), $value, vec![$($body),+])) +// }; +// +// ($name:literal = $value:expr => $($body:expr),+) => { +// Statement::Let(Let::new(miden_diagnostics::SourceSpan::UNKNOWN, ident!($name), $value, vec![$($body),+])) +// }; +//} +// +//macro_rules! enforce { +// ($expr:expr) => { +// Statement::Enforce($expr) +// }; +// +// ($expr:expr, when $selector:expr) => { +// Statement::EnforceIf($expr, $selector) +// }; +//} +// +//macro_rules! enforce_all { +// ($expr:expr) => { +// Statement::EnforceAll($expr) +// }; +//} +// +//macro_rules! lc { +// (($(($binding:ident, $iterable:expr)),+) => $body:expr) => {{ +// let context = vec![ +// $( +// (ident!($binding), $iterable) +// ),+ +// ]; +// ListComprehension::new(miden_diagnostics::SourceSpan::UNKNOWN, $body, context, None) +// }}; +// +// (($(($binding:literal, $iterable:expr)),+) => $body:expr) => {{ +// let context = vec![ +// $( +// (ident!($binding), $iterable) +// ),+ +// ]; +// ListComprehension::new(miden_diagnostics::SourceSpan::UNKNOWN, $body, context, None) +// }}; +// +// (($(($binding:ident, $iterable:expr)),*) => $body:expr, when $selector:expr) => {{ +// let context = vec![ +// $( +// (ident!($binding), $iterable) +// ),+ +// ]; +// ListComprehension::new(miden_diagnostics::SourceSpan::UNKNOWN, $body, context, Some($selector)) +// }}; +// +// (($(($binding:literal, $iterable:expr)),*) => $body:expr, when $selector:expr) => {{ +// let context = vec![ +// $( +// (ident!($binding), $iterable) +// ),+ +// ]; +// ListComprehension::new(miden_diagnostics::SourceSpan::UNKNOWN, $body, context, Some($selector)) +// }}; +//} +// +//macro_rules! range { +// ($range:expr) => { +// Expr::Range(miden_diagnostics::Span::new(SourceSpan::UNKNOWN, $range)) +// }; +//} +// +//macro_rules! and { +// ($lhs:expr, $rhs:expr) => { +// mul!($lhs, $rhs) +// }; +//} +// +//macro_rules! or { +// ($lhs:expr, $rhs:expr) => {{ +// sub!(add!($lhs, $rhs), mul!($lhs, $rhs)) +// }}; +//} +// +//macro_rules! not { +// ($rhs:expr) => { +// sub!(int!(1), $rhs) +// }; +//} +// +//macro_rules! eq { +// ($lhs:expr, $rhs:expr) => { +// ScalarExpr::Binary(BinaryExpr::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// BinaryOp::Eq, +// $lhs, +// $rhs, +// )) +// }; +//} +// +//macro_rules! add { +// ($lhs:expr, $rhs:expr) => { +// ScalarExpr::Binary(BinaryExpr::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// BinaryOp::Add, +// $lhs, +// $rhs, +// )) +// }; +//} +// +//macro_rules! sub { +// ($lhs:expr, $rhs:expr) => { +// ScalarExpr::Binary(BinaryExpr::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// BinaryOp::Sub, +// $lhs, +// $rhs, +// )) +// }; +//} +// +//macro_rules! mul { +// ($lhs:expr, $rhs:expr) => { +// ScalarExpr::Binary(BinaryExpr::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// BinaryOp::Mul, +// $lhs, +// $rhs, +// )) +// }; +//} +// +//macro_rules! exp { +// ($lhs:expr, $rhs:expr) => { +// ScalarExpr::Binary(BinaryExpr::new( +// miden_diagnostics::SourceSpan::UNKNOWN, +// BinaryOp::Exp, +// $lhs, +// $rhs, +// )) +// }; +//} +// +//macro_rules! import_all { +// ($module:ident) => { +// Import::All { +// module: ident!($module), +// } +// }; +//} +// +//macro_rules! import { +// ($module:ident, $item:ident) => {{ +// let mut items: std::collections::HashSet = std::collections::HashSet::default(); +// items.insert(ident!($item)); +// Import::Partial { +// module: ident!($module), +// items, +// } +// }}; +//} + +// FULL MIDEN IR FILE +// ================================================================================================ + +#[test] +fn full_mir_file() { + let dummy_sourcespan = miden_diagnostics::SourceSpan::UNKNOWN; + + // module miden_ir_test + let module_type = ModuleType::Module; + let module_name = ident!(miden_ir_test); + + // global_1 u32 internal = 0xCAFEBABE + let global_var_name = ident!(global_1); + let global_var_type = Type::U32; + let global_var_linkage = Linkage::Internal; + let mut global_var = GlobalVarDeclaration::new(dummy_sourcespan, + global_var_name, + global_var_type, + global_var_linkage); + let global_var_init_val : Vec = vec![b'C', b'A', b'F', b'E', b'B', b'A', b'B', b'E']; + let global_var_initializer = GlobalVarInitializer::new(global_var_init_val); + global_var.with_init(global_var_initializer); + let global_vars = vec![global_var]; + + // pub coc(fast) fn miden_ir_test::test_func (zext u32, sext u32) -> u32 { + let function_visibility = Visibility::Public; + let function_call_convention = CallConv::Fast; + let function_name = function_ident!(miden_ir_test, test_func); + + let fun_arg1_purpose = ArgumentPurpose::Default; + let fun_arg1_extension = ArgumentExtension::Zext; + let fun_arg1_type = Type::U32; + let fun_arg1 = FunctionParameter::new(fun_arg1_purpose, + fun_arg1_extension, + fun_arg1_type); + + let fun_arg2_purpose = ArgumentPurpose::Default; + let fun_arg2_extension = ArgumentExtension::Sext; + let fun_arg2_type = Type::U32; + let fun_arg2 = FunctionParameter::new(fun_arg2_purpose, + fun_arg2_extension, + fun_arg2_type); + let function_params = vec![fun_arg1, fun_arg2]; + + + let fun_return_extension = ArgumentExtension::None; + let fun_return_type = Type::U32; + let function_return = FunctionReturn::new(fun_return_extension, fun_return_type); + let function_returns = vec![function_return]; + + let function_signature = FunctionSignature::new(dummy_sourcespan, + function_visibility, + function_call_convention, + function_name, + function_params, + function_returns); + + // blk(v1 : u32, v2 : u32) : { + let block_label = Label::new(ident!(blk)); + + let block_arg1_name = Value::new(ident!(v1)); + let block_arg1_type = Type::U32; + let block_arg1 = BlockArgument::new(block_arg1_name, block_arg1_type); + + let block_arg2_name = Value::new(ident!(v2)); + let block_arg2_type = Type::U32; + let block_arg2 = BlockArgument::new(block_arg2_name, block_arg2_type); + + let block_args = vec![block_arg1, block_arg2]; + + let block_header = BlockHeader::new(block_label, block_args); + + // v3 = add.unchecked v1 v2 : u32 + let inst1_value = Value::new(ident!(v3)); + let inst1_values = vec![inst1_value]; + + let inst1_overflow = Overflow::Unchecked; + let inst1_opcode = BinaryOpCode::Add(inst1_overflow); + let inst1_operand1 = Value::new(ident!(v1)); + let inst1_operand2 = Value::new(ident!(v2)); + let inst1_op = Operation::BinaryOp(inst1_opcode, + inst1_operand1, + inst1_operand2); + let inst1_type = Type::U32; + let inst1_types : Vec = vec![inst1_type]; + let block_instruction1 = Instruction::new(dummy_sourcespan, + inst1_values, + inst1_op, + inst1_types); + + // ret (v1, v3) + let inst2_values : Vec = vec![]; + let inst2_return_val1 = Value::new(ident!(v1)); + let inst2_return_val2 = Value::new(ident!(v3)); + let inst2_return_vals = vec![inst2_return_val1, inst2_return_val2]; + let inst2_op = Operation::ReturnOp(inst2_return_vals); + let inst2_types : Vec = vec![]; + let block_instruction2 = Instruction::new(dummy_sourcespan, + inst2_values, + inst2_op, + inst2_types); + + let block_instructions = vec![block_instruction1, block_instruction2]; + + let block = Block::new(dummy_sourcespan, block_header, block_instructions); + let blocks = vec![block]; + + let function = FunctionDeclaration::new(dummy_sourcespan, + function_signature, + blocks); + let functions = vec![function]; + + // cc(kernel) fn exported::f1 (sret { u32, u32 }) -> [i8 ; 42]; + let external_visibility = Visibility::Private; + let external_call_convention = CallConv::Kernel; + let external_name = function_ident!(exported, f1); + + let ext_arg1_purpose = ArgumentPurpose::StructReturn; + let ext_arg1_extension = ArgumentExtension::None; + + let ext_arg1_type_field1 = Type::U32; + let ext_arg1_type_field2 = Type::U32; + let ext_arg1_type_fields = vec![ext_arg1_type_field1, ext_arg1_type_field2]; + let ext_arg1_type = Type::Struct(StructType::new(ext_arg1_type_fields)); + let ext_arg1 = FunctionParameter::new(ext_arg1_purpose, + ext_arg1_extension, + ext_arg1_type); + + let external_params = vec![ext_arg1]; + + let ext_return_extension = ArgumentExtension::None; + let ext_return_inner_type = Type::I8; + let ext_return_type = Type::Array(Box::new(ext_return_inner_type), 42); + let external_return = FunctionReturn::new(ext_return_extension, ext_return_type); + let external_returns = vec![external_return]; + + let external = FunctionSignature::new(dummy_sourcespan, + external_visibility, + external_call_convention, + external_name, + external_params, + external_returns); + let externals = vec![external]; + + let expected = Module::new(dummy_sourcespan, + module_type, + module_name, + global_vars, + functions, + externals); + + ParseTest::new().expect_module_ast_from_file("src/parser/parser/tests/input/system.mir", expected); +} diff --git a/hir/src/parser/parser/tests/utils.rs b/hir/src/parser/parser/tests/utils.rs new file mode 100644 index 00000000..e406fb73 --- /dev/null +++ b/hir/src/parser/parser/tests/utils.rs @@ -0,0 +1,156 @@ +use std::sync::Arc; + +use miden_diagnostics::{CodeMap, DiagnosticsConfig, DiagnosticsHandler, Emitter, Verbosity}; +use pretty_assertions::assert_eq; + +use crate::{ + parser::ast::Module, + parser::{ParseError, Parser}, +}; + +struct SplitEmitter { + capture: miden_diagnostics::CaptureEmitter, + default: miden_diagnostics::DefaultEmitter, +} +impl SplitEmitter { + #[inline] + pub fn new() -> Self { + use miden_diagnostics::term::termcolor::ColorChoice; + + Self { + capture: Default::default(), + default: miden_diagnostics::DefaultEmitter::new(ColorChoice::Auto), + } + } + + #[allow(unused)] + pub fn captured(&self) -> String { + self.capture.captured() + } +} +impl Emitter for SplitEmitter { + #[inline] + fn buffer(&self) -> miden_diagnostics::term::termcolor::Buffer { + self.capture.buffer() + } + + #[inline] + fn print(&self, buffer: miden_diagnostics::term::termcolor::Buffer) -> std::io::Result<()> { + use std::io::Write; + + let mut copy = self.capture.buffer(); + copy.write_all(buffer.as_slice())?; + self.capture.print(buffer)?; + self.default.print(copy) + } +} + +// TEST HANDLER +// ================================================================================================ + +/// [ParseTest] is a container for the data required to run parser tests. Used to build an AST from +/// the given source string and asserts that executing the test will result in the expected AST. +/// +/// # Errors: +/// - ScanError test: check that the source provided contains valid characters and keywords. +/// - ParseError test: check that the parsed values are valid. +/// * InvalidInt: This error is returned if the parsed number is not a valid u64. +pub struct ParseTest { + pub diagnostics: Arc, + #[allow(unused)] + emitter: Arc, + parser: Parser, +} + +impl ParseTest { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + + /// Creates a new test, from the source string. + pub fn new() -> Self { + let codemap = Arc::new(CodeMap::new()); + let emitter = Arc::new(SplitEmitter::new()); + let config = DiagnosticsConfig { + verbosity: Verbosity::Warning, + warnings_as_errors: true, + no_warn: false, + display: Default::default(), + }; + let diagnostics = Arc::new(DiagnosticsHandler::new( + config, + codemap.clone(), + emitter.clone(), + )); + let parser = Parser::new((), codemap); + Self { + diagnostics, + emitter, + parser, + } + } + + /// This adds a new in-memory file to the [CodeMap] for this test. + /// + /// This is used when we want to write a test with imports, without having to place files on disk + #[allow(unused)] + pub fn add_virtual_file>(&self, name: P, content: String) { + self.parser.codemap.add(name.as_ref(), content); + } + + pub fn parse_module_from_file(&self, path: &str) -> Result { + self.parser + .parse_file::(&self.diagnostics, path) + } + + #[allow(unused)] + pub fn parse_module(&self, source: &str) -> Result { + self.parser + .parse_string::(&self.diagnostics, source) + } + + // TEST METHODS + // -------------------------------------------------------------------------------------------- + + #[track_caller] + #[allow(unused)] + pub fn expect_module_diagnostic(&self, source: &str, expected: &str) { + if let Err(err) = self.parse_module(source) { + self.diagnostics.emit(err); + assert!( + self.emitter.captured().contains(expected), + "expected diagnostic output to contain the string: '{}'", + expected + ); + } else { + panic!("expected parsing to fail, but it succeeded"); + } + } + + /// Parses a [Module] from the given source string and asserts that executing the test will result + /// in the expected AST. + #[track_caller] + #[allow(unused)] + pub fn expect_module_ast(&self, source: &str, expected: Module) { + match self.parse_module(source) { + Err(err) => { + self.diagnostics.emit(err); + panic!("expected parsing to succeed, see diagnostics for details"); + } + Ok(ast) => assert_eq!(ast, expected), + } + } + + /// Parses a [Module] from the given source path and asserts that executing the test will result + /// in the expected AST. + #[allow(unused)] + #[track_caller] + pub fn expect_module_ast_from_file(&self, path: &str, expected: Module) { + match self.parse_module_from_file(path) { + Err(err) => { + self.diagnostics.emit(err); + panic!("expected parsing to succeed, see diagnostics for details"); + } + Ok(ast) => assert_eq!(ast, expected), + } + } +}