diff --git a/packages/macros/Cargo.lock b/packages/macros/Cargo.lock index a6f5d330..dec7619d 100644 --- a/packages/macros/Cargo.lock +++ b/packages/macros/Cargo.lock @@ -19,9 +19,11 @@ name = "alexandria_macros" version = "0.1.0" dependencies = [ "bigdecimal", + "cairo-lang-filesystem", "cairo-lang-macro", "cairo-lang-parser", "cairo-lang-syntax", + "cairo-lang-utils", ] [[package]] diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index f7647d2c..b2c206e6 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -11,3 +11,5 @@ bigdecimal = "0.4.7" cairo-lang-macro = "0.1.1" cairo-lang-parser = "2.9.2" cairo-lang-syntax = "2.9.2" +cairo-lang-filesystem = "2.9.2" +cairo-lang-utils = "2.9.2" diff --git a/packages/macros/src/pow.rs b/packages/macros/src/pow.rs index 5ad557c5..76058573 100644 --- a/packages/macros/src/pow.rs +++ b/packages/macros/src/pow.rs @@ -1,7 +1,12 @@ +use bigdecimal::num_bigint::BigInt; use bigdecimal::{num_traits::pow, BigDecimal}; + +use cairo_lang_filesystem::ids::{FileKind, FileLongId, VirtualFile}; use cairo_lang_macro::{inline_macro, Diagnostic, ProcMacroResult, TokenStream}; +use cairo_lang_parser::db::ParserGroup; use cairo_lang_parser::utils::SimpleParserDatabase; -use cairo_lang_syntax::node::kind::SyntaxKind::Arg; +use cairo_lang_syntax::node::ast::{ArgClause, Expr, ExprInlineMacro, WrappedArgList}; +use cairo_lang_utils::{Intern, Upcast}; /// Compile-time power function. /// @@ -14,41 +19,82 @@ use cairo_lang_syntax::node::kind::SyntaxKind::Arg; #[inline_macro] pub fn pow(token_stream: TokenStream) -> ProcMacroResult { let db = SimpleParserDatabase::default(); - let (parsed, _diag) = db.parse_virtual_with_diagnostics(token_stream); - - let macro_args: Vec = parsed - .descendants(&db) - .filter_map(|node| { - if let Arg = node.kind(&db) { - Some(node.get_text(&db)) - } else { - None - } - }) - .collect(); + // Get the ExprInlineMacro object so we can use the helper functions. + let mac = parse_inline_macro(token_stream, &db); + // Get the arguments of the macro. This macro expects a tuple as argument so we get the WrappedArgList::ParenthesizedArgList + let macro_args = if let WrappedArgList::ParenthesizedArgList(args) = mac.arguments(db.upcast()) + { + args.arguments(db.upcast()).elements(db.upcast()) + } else { + vec![] + }; if macro_args.len() != 2 { return ProcMacroResult::new(TokenStream::empty()) .with_diagnostics(Diagnostic::error("Invalid number of arguments").into()); } - - let base: BigDecimal = match macro_args[0].parse() { - Ok(val) => val, - Err(_) => { + let base = match get_arg_value(db.upcast(), ¯o_args[0].arg_clause(db.upcast())) { + Some(val) => val, + None => { return ProcMacroResult::new(TokenStream::empty()) - .with_diagnostics(Diagnostic::error("Invalid base value").into()); + .with_diagnostics(Diagnostic::error("Invalid base value").into()) } - }; + } + .into(); - let exp: usize = match macro_args[1].parse() { - Ok(val) => val, - Err(_) => { + let exp = match get_arg_value(db.upcast(), ¯o_args[1].arg_clause(db.upcast())) { + Some(val) => val, + None => { return ProcMacroResult::new(TokenStream::empty()) - .with_diagnostics(Diagnostic::error("Invalid exponent value").into()); + .with_diagnostics(Diagnostic::error("Invalid exponent value").into()) } + } + .try_into(); + let exp = if let Ok(exponent) = exp { + exponent + } else { + return ProcMacroResult::new(TokenStream::empty()) + .with_diagnostics(Diagnostic::error("Invalid exponent value").into()); }; let result: BigDecimal = pow(base, exp); ProcMacroResult::new(TokenStream::new(result.to_string())) } + +/// Return an [`ExprInlineMacro`] from the text received. The expected text is the macro arguments. +/// For example the initial macro text was `pow!(10, 3)`, the text in the token stream is only `(10, 3)` +fn parse_inline_macro(token_stream: impl ToString, db: &SimpleParserDatabase) -> ExprInlineMacro { + // Create a virtual file that will be parsed. + let file = FileLongId::Virtual(VirtualFile { + parent: None, + name: "parser_input".into(), + content: format!("pow!{}", token_stream.to_string()).into(), // easiest workaround after change + code_mappings: [].into(), + kind: FileKind::Expr, // this part is different than db.parse_virtual + }) + .intern(db); + + // Could fail if there was a parsing error but it shouldn't happen as the file has already + // been parsed once to reach this macro. + let node = db.file_expr_syntax(file).unwrap(); + + let Expr::InlineMacro(inline_macro) = node else { + panic!() // should not happen + }; + + inline_macro +} + +/// Returns the value of a literal argument. +fn get_arg_value(db: &SimpleParserDatabase, arg_clause: &ArgClause) -> Option { + let base_expr = match arg_clause { + ArgClause::Unnamed(arg_clause) => arg_clause.value(db.upcast()), + _ => return None, + }; + if let Expr::Literal(base_lit) = base_expr { + base_lit.numeric_value(db.upcast()) + } else { + None + } +}