diff --git a/crates/cairo-lang-plugins/src/lib.rs b/crates/cairo-lang-plugins/src/lib.rs index 8f433858f73..cfbd5b54e24 100644 --- a/crates/cairo-lang-plugins/src/lib.rs +++ b/crates/cairo-lang-plugins/src/lib.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use cairo_lang_semantic::plugin::SemanticPlugin; use crate::plugins::{ - ConfigPlugin, ConstevalIntMacroPlugin, DerivePlugin, GenerateTraitPlugin, PanicablePlugin, + ConfigPlugin, DerivePlugin, GenerateTraitPlugin, InlineMacroPlugin, PanicablePlugin, }; pub mod plugins; @@ -15,10 +15,10 @@ mod test; /// Gets the list of default plugins to load into the Cairo compiler. pub fn get_default_plugins() -> Vec> { vec![ - Arc::new(ConstevalIntMacroPlugin::default()), Arc::new(DerivePlugin::default()), Arc::new(GenerateTraitPlugin::default()), Arc::new(PanicablePlugin::default()), Arc::new(ConfigPlugin::default()), + Arc::new(InlineMacroPlugin::default()), ] } diff --git a/crates/cairo-lang-plugins/src/plugins/inline_macro_plugin.rs b/crates/cairo-lang-plugins/src/plugins/inline_macro_plugin.rs new file mode 100644 index 00000000000..cd7f124ba3f --- /dev/null +++ b/crates/cairo-lang-plugins/src/plugins/inline_macro_plugin.rs @@ -0,0 +1,144 @@ +use std::sync::Arc; + +use cairo_lang_defs::plugin::{ + DynGeneratedFileAuxData, MacroPlugin, PluginDiagnostic, PluginGeneratedFile, PluginResult, +}; +use cairo_lang_semantic::plugin::{AsDynMacroPlugin, SemanticPlugin, TrivialPluginAuxData}; +use cairo_lang_syntax::node::ast::{self}; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::kind::SyntaxKind; +use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode}; + +use super::inline_macros::consteval_int::ConstevalIntMacro; + +/// The result of expanding an inline macro. +#[derive(Debug, Default)] +pub struct InlineMacroExpanderData { + pub result_code: String, + pub code_changed: bool, + pub diagnostics: Vec, +} + +/// A trait for inline macros. +pub trait InlineMacro { + /// A function that appends the expanded code of the macro to the result code. + fn append_macro_code( + &self, + macro_expander_data: &mut InlineMacroExpanderData, + db: &dyn SyntaxGroup, + macro_arguments: &ast::ExprList, + ); + /// A function that returns true if the macro supports the given bracket type. + fn is_bracket_type_allowed( + &self, + db: &dyn SyntaxGroup, + macro_ast: &ast::ExprInlineMacro, + ) -> bool; +} + +/// Returns the inline macro plugin for the given macro name, or None if no such plugin exists. +fn get_inline_macro_plugin(macro_name: &str) -> Option> { + match macro_name { + "consteval_int" => Some(Box::new(ConstevalIntMacro)), + _ => None, + } +} + +#[derive(Debug, Default)] +pub struct InlineMacroPlugin; +impl MacroPlugin for InlineMacroPlugin { + fn generate_code(&self, db: &dyn SyntaxGroup, item_ast: ast::Item) -> PluginResult { + let mut expander_data = InlineMacroExpanderData::default(); + expander_data.expand_node(db, &item_ast.as_syntax_node()); + if expander_data.code_changed { + PluginResult { + code: Some(PluginGeneratedFile { + name: "inline_macros".into(), + content: expander_data.result_code.clone(), + aux_data: DynGeneratedFileAuxData(Arc::new(TrivialPluginAuxData {})), + }), + diagnostics: expander_data.diagnostics, + remove_original_item: true, + } + } else { + PluginResult { + code: None, + diagnostics: expander_data.diagnostics, + remove_original_item: false, + } + } + } +} + +impl AsDynMacroPlugin for InlineMacroPlugin { + fn as_dyn_macro_plugin<'a>(self: Arc) -> Arc + where + Self: 'a, + { + self + } +} +impl SemanticPlugin for InlineMacroPlugin {} + +impl InlineMacroExpanderData { + /// Traverse the syntax tree, accumolates any non-macro code and expand all inline macros. + fn expand_node(&mut self, db: &dyn SyntaxGroup, syntax_node: &SyntaxNode) { + let node_kind = syntax_node.kind(db); + if let SyntaxKind::ExprInlineMacro = node_kind { + let inline_macro = ast::ExprInlineMacro::from_syntax_node(db, syntax_node.clone()); + self.handle_macro(db, &inline_macro); + } else { + if let Some(text) = syntax_node.text(db) { + self.result_code.push_str(&text); + } + for child in syntax_node.children(db) { + self.expand_node(db, &child); + } + } + } + + /// Expand a single inline macro. + fn handle_macro(&mut self, db: &dyn SyntaxGroup, inline_macro: &ast::ExprInlineMacro) { + let macro_name = inline_macro.path(db).as_syntax_node().get_text(db); + let macro_plugin = get_inline_macro_plugin(¯o_name); + if let Some(macro_plugin) = macro_plugin { + if let Some(macro_arguments) = + self.extract_macro_args(db, macro_plugin.as_ref(), inline_macro) + { + macro_plugin.append_macro_code(self, db, ¯o_arguments); + } + } else { + self.result_code.push_str(&inline_macro.as_syntax_node().get_text(db)); + self.diagnostics.push(PluginDiagnostic { + stable_ptr: inline_macro.stable_ptr().untyped(), + message: format!("Unknown inline macro: {}", macro_name), + }); + } + } + + /// Extract the macro arguments from the inline macro if the macro supports the given bracket + /// type. Otherwise, add a diagnostic. + fn extract_macro_args( + &mut self, + db: &dyn SyntaxGroup, + macro_plugin: &dyn InlineMacro, + macro_ast: &ast::ExprInlineMacro, + ) -> Option { + if macro_plugin.is_bracket_type_allowed(db, macro_ast) { + Some(match macro_ast.arguments(db) { + ast::WrappedExprList::BracketedExprList(expr_list) => expr_list.expressions(db), + ast::WrappedExprList::ParenthesizedExprList(expr_list) => expr_list.expressions(db), + ast::WrappedExprList::BracedExprList(expr_list) => expr_list.expressions(db), + }) + } else { + self.diagnostics.push(PluginDiagnostic { + stable_ptr: macro_ast.stable_ptr().untyped(), + message: format!( + "Macro {} does not support this bracket type", + macro_ast.path(db).as_syntax_node().get_text(db) + ), + }); + None + } + } +} diff --git a/crates/cairo-lang-plugins/src/plugins/consteval_int.rs b/crates/cairo-lang-plugins/src/plugins/inline_macros/consteval_int.rs similarity index 55% rename from crates/cairo-lang-plugins/src/plugins/consteval_int.rs rename to crates/cairo-lang-plugins/src/plugins/inline_macros/consteval_int.rs index a8c4cf25ae1..cb4484306b2 100644 --- a/crates/cairo-lang-plugins/src/plugins/consteval_int.rs +++ b/crates/cairo-lang-plugins/src/plugins/inline_macros/consteval_int.rs @@ -1,102 +1,66 @@ -use std::sync::Arc; - -use cairo_lang_defs::plugin::{ - DynGeneratedFileAuxData, MacroPlugin, PluginDiagnostic, PluginGeneratedFile, PluginResult, -}; -use cairo_lang_semantic::plugin::{AsDynMacroPlugin, SemanticPlugin, TrivialPluginAuxData}; +use cairo_lang_defs::plugin::PluginDiagnostic; use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; +use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; use num_bigint::BigInt; -#[derive(Debug, Default)] -#[non_exhaustive] -pub struct ConstevalIntMacroPlugin; - -impl AsDynMacroPlugin for ConstevalIntMacroPlugin { - fn as_dyn_macro_plugin<'a>(self: Arc) -> Arc - where - Self: 'a, - { - self - } -} -impl SemanticPlugin for ConstevalIntMacroPlugin {} +use crate::plugins::{InlineMacro, InlineMacroExpanderData}; -impl MacroPlugin for ConstevalIntMacroPlugin { - fn generate_code(&self, db: &dyn SyntaxGroup, item_ast: ast::Item) -> PluginResult { - match item_ast { - ast::Item::Constant(constant_ast) => handle_constant(db, &constant_ast), - _ => PluginResult::default(), - } - } -} +pub struct ConstevalIntMacro; -/// Rewrite a constant declaration that contains a consteval_int macro -/// into a constant declaration with the computed value, -/// e.g. `const a: felt252 = consteval_int!(2 * 2 * 2);` into `const a: felt252 = 8;`. -fn handle_constant(db: &dyn SyntaxGroup, constant_ast: &ast::ItemConstant) -> PluginResult { - let constant_value = constant_ast.value(db); - if let ast::Expr::InlineMacro(inline_macro) = constant_value { - if inline_macro.path(db).as_syntax_node().get_text(db) != "consteval_int" { - return PluginResult::default(); - } - let mut diagnostics = vec![]; - let constant_expression = - extract_consteval_macro_expression(db, &inline_macro, &mut diagnostics); +impl InlineMacro for ConstevalIntMacro { + fn append_macro_code( + &self, + macro_expander_data: &mut InlineMacroExpanderData, + db: &dyn SyntaxGroup, + macro_arguments: &ast::ExprList, + ) { + let constant_expression = extract_consteval_macro_expression( + db, + macro_arguments, + &mut macro_expander_data.diagnostics, + ); if constant_expression.is_none() { - return PluginResult { diagnostics, ..Default::default() }; + return; } - let new_value = compute_constant_expr(db, &constant_expression.unwrap(), &mut diagnostics); - if new_value.is_none() { - return PluginResult { diagnostics, ..Default::default() }; + if let Some(new_value) = compute_constant_expr( + db, + &constant_expression.unwrap(), + &mut macro_expander_data.diagnostics, + ) { + macro_expander_data.result_code.push_str(&new_value.to_string()); + macro_expander_data.code_changed = true; } - return PluginResult { - code: Some(PluginGeneratedFile { - name: "computed_constants".into(), - content: format!( - "const {}{}= {};", - constant_ast.name(db).text(db), - constant_ast.type_clause(db).as_syntax_node().get_text(db), - new_value.unwrap() - ), - aux_data: DynGeneratedFileAuxData(Arc::new(TrivialPluginAuxData {})), - }), - diagnostics, - remove_original_item: true, - }; } - PluginResult::default() + + fn is_bracket_type_allowed( + &self, + db: &dyn SyntaxGroup, + macro_ast: &ast::ExprInlineMacro, + ) -> bool { + matches!(macro_ast.arguments(db), ast::WrappedExprList::ParenthesizedExprList(_)) + } } /// Extract the actual expression from the consteval_int macro, or fail with diagnostics. -fn extract_consteval_macro_expression( +pub fn extract_consteval_macro_expression( db: &dyn SyntaxGroup, - macro_ast: &ast::ExprInlineMacro, + macro_arguments: &ast::ExprList, diagnostics: &mut Vec, ) -> Option { - let wrapped_args = macro_ast.arguments(db); - let exprs = match wrapped_args { - ast::WrappedExprList::BracketedExprList(args_list) => { - args_list.expressions(db).elements(db) - } - ast::WrappedExprList::ParenthesizedExprList(args_list) => { - args_list.expressions(db).elements(db) - } - ast::WrappedExprList::BracedExprList(args_list) => args_list.expressions(db).elements(db), - }; - if exprs.len() != 1 { + let arguments = macro_arguments.elements(db); + if arguments.len() != 1 { diagnostics.push(PluginDiagnostic { - stable_ptr: macro_ast.stable_ptr().untyped(), + stable_ptr: macro_arguments.stable_ptr().untyped(), message: "consteval_int macro must have a single unnamed argument.".to_string(), }); return None; } - Some(exprs[0].clone()) + Some(arguments[0].clone()) } /// Compute the actual value of an integer expression, or fail with diagnostics. /// This computation handles arbitrary integers, unlike regular Cairo math. -fn compute_constant_expr( +pub fn compute_constant_expr( db: &dyn SyntaxGroup, value: &ast::Expr, diagnostics: &mut Vec, diff --git a/crates/cairo-lang-plugins/src/plugins/inline_macros/mod.rs b/crates/cairo-lang-plugins/src/plugins/inline_macros/mod.rs new file mode 100644 index 00000000000..1ee3a38b44d --- /dev/null +++ b/crates/cairo-lang-plugins/src/plugins/inline_macros/mod.rs @@ -0,0 +1 @@ +pub mod consteval_int; diff --git a/crates/cairo-lang-plugins/src/plugins/mod.rs b/crates/cairo-lang-plugins/src/plugins/mod.rs index baec02d3bc0..bb49dbd0c05 100644 --- a/crates/cairo-lang-plugins/src/plugins/mod.rs +++ b/crates/cairo-lang-plugins/src/plugins/mod.rs @@ -1,11 +1,12 @@ pub use config::*; -pub use consteval_int::*; pub use derive::*; pub use generate_trait::*; +pub use inline_macro_plugin::*; pub use panicable::*; mod config; -mod consteval_int; mod derive; mod generate_trait; +mod inline_macro_plugin; +mod inline_macros; mod panicable; diff --git a/crates/cairo-lang-plugins/src/test.rs b/crates/cairo-lang-plugins/src/test.rs index dcdea1121a6..0815c918437 100644 --- a/crates/cairo-lang-plugins/src/test.rs +++ b/crates/cairo-lang-plugins/src/test.rs @@ -15,7 +15,7 @@ cairo_lang_test_utils::test_file_test!( expand_plugin, "src/test_data", { - consteval_int: "consteval_int", + inline_macros: "inline_macros", config: "config", derive: "derive", generate_trait: "generate_trait", diff --git a/crates/cairo-lang-plugins/src/test_data/consteval_int b/crates/cairo-lang-plugins/src/test_data/inline_macros similarity index 59% rename from crates/cairo-lang-plugins/src/test_data/consteval_int rename to crates/cairo-lang-plugins/src/test_data/inline_macros index 4a21b73f49d..3f6ba1241f2 100644 --- a/crates/cairo-lang-plugins/src/test_data/consteval_int +++ b/crates/cairo-lang-plugins/src/test_data/inline_macros @@ -17,11 +17,16 @@ const e: u8 = consteval_int!(255 + 1 - 1); //! > generated_cairo_code const a: felt252 = 0; + const b: felt252 = 9; + const c: felt252 = 4 + 5; + const d: felt252 = 47; + + const e: u8 = 255; //! > expected_diagnostics @@ -44,6 +49,10 @@ const d: felt252 = consteval_int!(~24); const e: felt252 = consteval_int!(234 < 5); +const e: felt252 = consteval_int![4 + 5]; + +const f: felt252 = consteval_int!{4 + 5}; + //! > generated_cairo_code const a: felt252 = consteval_int!(func_call(24)); @@ -59,6 +68,12 @@ const d: felt252 = consteval_int!(~24); const e: felt252 = consteval_int!(234 < 5); + +const e: felt252 = consteval_int![4 + 5]; + + +const f: felt252 = consteval_int!{4 + 5}; + //! > expected_diagnostics error: Unsupported expression in consteval_int macro --> dummy_file.cairo:1:35 @@ -85,9 +100,19 @@ error: Unsupported binary operator in consteval_int macro const e: felt252 = consteval_int!(234 < 5); ^*****^ +error: Macro consteval_int does not support this bracket type + --> dummy_file.cairo:11:20 +const e: felt252 = consteval_int![4 + 5]; + ^*******************^ + +error: Macro consteval_int does not support this bracket type + --> dummy_file.cairo:13:20 +const f: felt252 = consteval_int!{4 + 5}; + ^*******************^ + //! > ========================================================================== -//! > Test consteval_int! inside functions (currently does nothing) +//! > Test consteval_int! inside functions //! > test_runner_name test_expand_plugin @@ -101,7 +126,55 @@ fn some_func() //! > generated_cairo_code fn some_func() { - return consteval_int!(4 + 5); + return 9; +} + +//! > expected_diagnostics + +//! > ========================================================================== + +//! > Test recursive consteval_int! + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +fn some_func() +{ + return consteval_int!(4 + consteval_int!(2+3)); +} + +//! > generated_cairo_code +fn some_func() +{ + return consteval_int!(4 + consteval_int!(2+3)); +} + +//! > expected_diagnostics +error: Unsupported expression in consteval_int macro + --> dummy_file.cairo:3:31 + return consteval_int!(4 + consteval_int!(2+3)); + ^*****************^ + +//! > ========================================================================== + +//! > Test unknwon macro + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +fn foo() { + let x = foo!(0); +} + +//! > generated_cairo_code +fn foo() { + let x = foo!(0); } //! > expected_diagnostics +error: Unknown inline macro: foo + --> dummy_file.cairo:2:13 + let x = foo!(0); + ^*****^