Skip to content

Commit

Permalink
Add inline macro plugin and use it for consteval_int.
Browse files Browse the repository at this point in the history
  • Loading branch information
gilbens-starkware committed Jun 28, 2023
1 parent c210b76 commit de9b26d
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 82 deletions.
4 changes: 2 additions & 2 deletions crates/cairo-lang-plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,10 +15,10 @@ mod test;
/// Gets the list of default plugins to load into the Cairo compiler.
pub fn get_default_plugins() -> Vec<Arc<dyn SemanticPlugin>> {
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()),
]
}
144 changes: 144 additions & 0 deletions crates/cairo-lang-plugins/src/plugins/inline_macro_plugin.rs
Original file line number Diff line number Diff line change
@@ -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<PluginDiagnostic>,
}

/// 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<Box<dyn InlineMacro>> {
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<Self>) -> Arc<dyn MacroPlugin + 'a>
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(&macro_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, &macro_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<ast::ExprList> {
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
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Self>) -> Arc<dyn MacroPlugin + 'a>
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<PluginDiagnostic>,
) -> Option<ast::Expr> {
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<PluginDiagnostic>,
Expand Down
1 change: 1 addition & 0 deletions crates/cairo-lang-plugins/src/plugins/inline_macros/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod consteval_int;
5 changes: 3 additions & 2 deletions crates/cairo-lang-plugins/src/plugins/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion crates/cairo-lang-plugins/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading

0 comments on commit de9b26d

Please sign in to comment.