From a36d91cd69694cbae2d4be8e5435d7c79d8837bc Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 15:42:13 +0200 Subject: [PATCH 01/18] feat(rengine): make `Fragment[Ref]` complete --- rust-engine/src/ast/fragment.rs | 672 +++++--------------------------- 1 file changed, 103 insertions(+), 569 deletions(-) diff --git a/rust-engine/src/ast/fragment.rs b/rust-engine/src/ast/fragment.rs index bafeec175..589df8196 100644 --- a/rust-engine/src/ast/fragment.rs +++ b/rust-engine/src/ast/fragment.rs @@ -5,575 +5,109 @@ use crate::ast::*; -impl From for Fragment { - fn from(item: GenericValue) -> Self { - Self::GenericValue(item) - } -} -impl From for Fragment { - fn from(item: PrimitiveTy) -> Self { - Self::PrimitiveTy(item) - } -} -impl From for Fragment { - fn from(item: Region) -> Self { - Self::Region(item) - } -} -impl From for Fragment { - fn from(item: Ty) -> Self { - Self::Ty(item) - } -} -impl From for Fragment { - fn from(item: DynTraitGoal) -> Self { - Self::DynTraitGoal(item) - } -} -impl From for Fragment { - fn from(item: Metadata) -> Self { - Self::Metadata(item) - } -} -impl From for Fragment { - fn from(item: Expr) -> Self { - Self::Expr(item) - } -} -impl From for Fragment { - fn from(item: Pat) -> Self { - Self::Pat(item) - } -} -impl From for Fragment { - fn from(item: Arm) -> Self { - Self::Arm(item) - } -} -impl From for Fragment { - fn from(item: Guard) -> Self { - Self::Guard(item) - } -} -impl From for Fragment { - fn from(item: BorrowKind) -> Self { - Self::BorrowKind(item) - } -} -impl From for Fragment { - fn from(item: BindingMode) -> Self { - Self::BindingMode(item) - } -} -impl From for Fragment { - fn from(item: PatKind) -> Self { - Self::PatKind(item) - } -} -impl From for Fragment { - fn from(item: GuardKind) -> Self { - Self::GuardKind(item) - } -} -impl From for Fragment { - fn from(item: Lhs) -> Self { - Self::Lhs(item) - } -} -impl From for Fragment { - fn from(item: ImplExpr) -> Self { - Self::ImplExpr(item) - } -} -impl From for Fragment { - fn from(item: ImplExprKind) -> Self { - Self::ImplExprKind(item) - } -} -impl From for Fragment { - fn from(item: ImplItem) -> Self { - Self::ImplItem(item) - } -} -impl From for Fragment { - fn from(item: ImplItemKind) -> Self { - Self::ImplItemKind(item) - } -} -impl From for Fragment { - fn from(item: TraitItem) -> Self { - Self::TraitItem(item) - } -} -impl From for Fragment { - fn from(item: TraitItemKind) -> Self { - Self::TraitItemKind(item) - } -} -impl From for Fragment { - fn from(item: QuoteContent) -> Self { - Self::QuoteContent(item) - } -} -impl From for Fragment { - fn from(item: Quote) -> Self { - Self::Quote(item) - } -} -impl From for Fragment { - fn from(item: ItemQuoteOrigin) -> Self { - Self::ItemQuoteOrigin(item) - } -} -impl From for Fragment { - fn from(item: ItemQuoteOriginKind) -> Self { - Self::ItemQuoteOriginKind(item) - } -} -impl From for Fragment { - fn from(item: ItemQuoteOriginPosition) -> Self { - Self::ItemQuoteOriginPosition(item) - } -} -impl From for Fragment { - fn from(item: LoopKind) -> Self { - Self::LoopKind(item) - } -} -impl From for Fragment { - fn from(item: Literal) -> Self { - Self::Literal(item) - } -} -impl From for Fragment { - fn from(item: ControlFlowKind) -> Self { - Self::ControlFlowKind(item) - } -} -impl From for Fragment { - fn from(item: LoopState) -> Self { - Self::LoopState(item) - } -} -impl From for Fragment { - fn from(item: ExprKind) -> Self { - Self::ExprKind(item) - } -} -impl From for Fragment { - fn from(item: GenericParamKind) -> Self { - Self::GenericParamKind(item) - } -} -impl From for Fragment { - fn from(item: TraitGoal) -> Self { - Self::TraitGoal(item) - } -} -impl From for Fragment { - fn from(item: ImplIdent) -> Self { - Self::ImplIdent(item) - } -} -impl From for Fragment { - fn from(item: ProjectionPredicate) -> Self { - Self::ProjectionPredicate(item) - } -} -impl From for Fragment { - fn from(item: GenericConstraint) -> Self { - Self::GenericConstraint(item) - } -} -impl From for Fragment { - fn from(item: GenericParam) -> Self { - Self::GenericParam(item) - } -} -impl From for Fragment { - fn from(item: Generics) -> Self { - Self::Generics(item) - } -} -impl From for Fragment { - fn from(item: SafetyKind) -> Self { - Self::SafetyKind(item) - } -} -impl From for Fragment { - fn from(item: Attribute) -> Self { - Self::Attribute(item) - } -} -impl From for Fragment { - fn from(item: AttributeKind) -> Self { - Self::AttributeKind(item) - } -} -impl From for Fragment { - fn from(item: DocCommentKind) -> Self { - Self::DocCommentKind(item) - } -} -impl From for Fragment { - fn from(item: SpannedTy) -> Self { - Self::SpannedTy(item) - } -} -impl From for Fragment { - fn from(item: Param) -> Self { - Self::Param(item) - } -} -impl From for Fragment { - fn from(item: Variant) -> Self { - Self::Variant(item) - } -} -impl From for Fragment { - fn from(item: ItemKind) -> Self { - Self::ItemKind(item) - } -} -impl From for Fragment { - fn from(item: Item) -> Self { - Self::Item(item) - } -} +/// Macro that derives automatically the `Fragment` and `FragmentRef` enumerations. +macro_rules! mk { + ($($ty:ident),*) => { + #[derive_group_for_ast] + #[allow(missing_docs)] + /// An owned fragment of AST in hax. + pub enum Fragment { + $( + #[doc = concat!("An owned [`", stringify!($ty), "`] node.")] + $ty($ty), + )* + /// Represent an unknown node in the AST with a message. + Unknown(String), + } + #[derive(Copy)] + #[derive_group_for_ast_base] + #[derive(::serde::Serialize)] + #[allow(missing_docs)] + /// A borrowed fragment of AST in hax. + pub enum FragmentRef<'lt> { + $( + #[doc = concat!("A borrowed [`", stringify!($ty), "`] node.")] + $ty(&'lt $ty), + )* + } -/// An owned fragment of the AST: this enumeration can represent any node in the AST. -#[allow(missing_docs)] -#[derive_group_for_ast] -pub enum Fragment { - GenericValue(GenericValue), - PrimitiveTy(PrimitiveTy), - Region(Region), - Ty(Ty), - DynTraitGoal(DynTraitGoal), - Metadata(Metadata), - Expr(Expr), - Pat(Pat), - Arm(Arm), - Guard(Guard), - BorrowKind(BorrowKind), - BindingMode(BindingMode), - PatKind(PatKind), - GuardKind(GuardKind), - Lhs(Lhs), - ImplExpr(ImplExpr), - ImplExprKind(ImplExprKind), - ImplItem(ImplItem), - ImplItemKind(ImplItemKind), - TraitItem(TraitItem), - TraitItemKind(TraitItemKind), - QuoteContent(QuoteContent), - Quote(Quote), - ItemQuoteOrigin(ItemQuoteOrigin), - ItemQuoteOriginKind(ItemQuoteOriginKind), - ItemQuoteOriginPosition(ItemQuoteOriginPosition), - LoopKind(LoopKind), - Literal(Literal), - ControlFlowKind(ControlFlowKind), - LoopState(LoopState), - ExprKind(ExprKind), - GenericParamKind(GenericParamKind), - TraitGoal(TraitGoal), - ImplIdent(ImplIdent), - ProjectionPredicate(ProjectionPredicate), - GenericConstraint(GenericConstraint), - GenericParam(GenericParam), - Generics(Generics), - SafetyKind(SafetyKind), - Attribute(Attribute), - AttributeKind(AttributeKind), - DocCommentKind(DocCommentKind), - SpannedTy(SpannedTy), - Param(Param), - Variant(Variant), - ItemKind(ItemKind), - Item(Item), - Unknown(String), -} -impl<'lt> From<&'lt GenericValue> for FragmentRef<'lt> { - fn from(item: &'lt GenericValue) -> Self { - Self::GenericValue(item) - } -} -impl<'lt> From<&'lt PrimitiveTy> for FragmentRef<'lt> { - fn from(item: &'lt PrimitiveTy) -> Self { - Self::PrimitiveTy(item) - } -} -impl<'lt> From<&'lt Region> for FragmentRef<'lt> { - fn from(item: &'lt Region) -> Self { - Self::Region(item) - } -} -impl<'lt> From<&'lt Ty> for FragmentRef<'lt> { - fn from(item: &'lt Ty) -> Self { - Self::Ty(item) - } -} -impl<'lt> From<&'lt DynTraitGoal> for FragmentRef<'lt> { - fn from(item: &'lt DynTraitGoal) -> Self { - Self::DynTraitGoal(item) - } -} -impl<'lt> From<&'lt Metadata> for FragmentRef<'lt> { - fn from(item: &'lt Metadata) -> Self { - Self::Metadata(item) - } -} -impl<'lt> From<&'lt Expr> for FragmentRef<'lt> { - fn from(item: &'lt Expr) -> Self { - Self::Expr(item) - } -} -impl<'lt> From<&'lt Pat> for FragmentRef<'lt> { - fn from(item: &'lt Pat) -> Self { - Self::Pat(item) - } -} -impl<'lt> From<&'lt Arm> for FragmentRef<'lt> { - fn from(item: &'lt Arm) -> Self { - Self::Arm(item) - } -} -impl<'lt> From<&'lt Guard> for FragmentRef<'lt> { - fn from(item: &'lt Guard) -> Self { - Self::Guard(item) - } -} -impl<'lt> From<&'lt BorrowKind> for FragmentRef<'lt> { - fn from(item: &'lt BorrowKind) -> Self { - Self::BorrowKind(item) - } -} -impl<'lt> From<&'lt BindingMode> for FragmentRef<'lt> { - fn from(item: &'lt BindingMode) -> Self { - Self::BindingMode(item) - } -} -impl<'lt> From<&'lt PatKind> for FragmentRef<'lt> { - fn from(item: &'lt PatKind) -> Self { - Self::PatKind(item) - } -} -impl<'lt> From<&'lt GuardKind> for FragmentRef<'lt> { - fn from(item: &'lt GuardKind) -> Self { - Self::GuardKind(item) - } -} -impl<'lt> From<&'lt Lhs> for FragmentRef<'lt> { - fn from(item: &'lt Lhs) -> Self { - Self::Lhs(item) - } -} -impl<'lt> From<&'lt ImplExpr> for FragmentRef<'lt> { - fn from(item: &'lt ImplExpr) -> Self { - Self::ImplExpr(item) - } -} -impl<'lt> From<&'lt ImplExprKind> for FragmentRef<'lt> { - fn from(item: &'lt ImplExprKind) -> Self { - Self::ImplExprKind(item) - } -} -impl<'lt> From<&'lt ImplItem> for FragmentRef<'lt> { - fn from(item: &'lt ImplItem) -> Self { - Self::ImplItem(item) - } -} -impl<'lt> From<&'lt ImplItemKind> for FragmentRef<'lt> { - fn from(item: &'lt ImplItemKind) -> Self { - Self::ImplItemKind(item) - } -} -impl<'lt> From<&'lt TraitItem> for FragmentRef<'lt> { - fn from(item: &'lt TraitItem) -> Self { - Self::TraitItem(item) - } -} -impl<'lt> From<&'lt TraitItemKind> for FragmentRef<'lt> { - fn from(item: &'lt TraitItemKind) -> Self { - Self::TraitItemKind(item) - } -} -impl<'lt> From<&'lt QuoteContent> for FragmentRef<'lt> { - fn from(item: &'lt QuoteContent) -> Self { - Self::QuoteContent(item) - } -} -impl<'lt> From<&'lt Quote> for FragmentRef<'lt> { - fn from(item: &'lt Quote) -> Self { - Self::Quote(item) - } -} -impl<'lt> From<&'lt ItemQuoteOrigin> for FragmentRef<'lt> { - fn from(item: &'lt ItemQuoteOrigin) -> Self { - Self::ItemQuoteOrigin(item) - } -} -impl<'lt> From<&'lt ItemQuoteOriginKind> for FragmentRef<'lt> { - fn from(item: &'lt ItemQuoteOriginKind) -> Self { - Self::ItemQuoteOriginKind(item) - } -} -impl<'lt> From<&'lt ItemQuoteOriginPosition> for FragmentRef<'lt> { - fn from(item: &'lt ItemQuoteOriginPosition) -> Self { - Self::ItemQuoteOriginPosition(item) - } -} -impl<'lt> From<&'lt LoopKind> for FragmentRef<'lt> { - fn from(item: &'lt LoopKind) -> Self { - Self::LoopKind(item) - } -} -impl<'lt> From<&'lt ControlFlowKind> for FragmentRef<'lt> { - fn from(item: &'lt ControlFlowKind) -> Self { - Self::ControlFlowKind(item) - } -} -impl<'lt> From<&'lt LoopState> for FragmentRef<'lt> { - fn from(item: &'lt LoopState) -> Self { - Self::LoopState(item) - } -} -impl<'lt> From<&'lt ExprKind> for FragmentRef<'lt> { - fn from(item: &'lt ExprKind) -> Self { - Self::ExprKind(item) - } -} -impl<'lt> From<&'lt GenericParamKind> for FragmentRef<'lt> { - fn from(item: &'lt GenericParamKind) -> Self { - Self::GenericParamKind(item) - } -} -impl<'lt> From<&'lt TraitGoal> for FragmentRef<'lt> { - fn from(item: &'lt TraitGoal) -> Self { - Self::TraitGoal(item) - } -} -impl<'lt> From<&'lt ImplIdent> for FragmentRef<'lt> { - fn from(item: &'lt ImplIdent) -> Self { - Self::ImplIdent(item) - } -} -impl<'lt> From<&'lt ProjectionPredicate> for FragmentRef<'lt> { - fn from(item: &'lt ProjectionPredicate) -> Self { - Self::ProjectionPredicate(item) - } -} -impl<'lt> From<&'lt GenericConstraint> for FragmentRef<'lt> { - fn from(item: &'lt GenericConstraint) -> Self { - Self::GenericConstraint(item) - } -} -impl<'lt> From<&'lt GenericParam> for FragmentRef<'lt> { - fn from(item: &'lt GenericParam) -> Self { - Self::GenericParam(item) - } -} -impl<'lt> From<&'lt Generics> for FragmentRef<'lt> { - fn from(item: &'lt Generics) -> Self { - Self::Generics(item) - } -} -impl<'lt> From<&'lt SafetyKind> for FragmentRef<'lt> { - fn from(item: &'lt SafetyKind) -> Self { - Self::SafetyKind(item) - } -} -impl<'lt> From<&'lt Attribute> for FragmentRef<'lt> { - fn from(item: &'lt Attribute) -> Self { - Self::Attribute(item) - } -} -impl<'lt> From<&'lt AttributeKind> for FragmentRef<'lt> { - fn from(item: &'lt AttributeKind) -> Self { - Self::AttributeKind(item) - } -} -impl<'lt> From<&'lt DocCommentKind> for FragmentRef<'lt> { - fn from(item: &'lt DocCommentKind) -> Self { - Self::DocCommentKind(item) - } -} -impl<'lt> From<&'lt SpannedTy> for FragmentRef<'lt> { - fn from(item: &'lt SpannedTy) -> Self { - Self::SpannedTy(item) - } -} -impl<'lt> From<&'lt Param> for FragmentRef<'lt> { - fn from(item: &'lt Param) -> Self { - Self::Param(item) - } -} -impl<'lt> From<&'lt Variant> for FragmentRef<'lt> { - fn from(item: &'lt Variant) -> Self { - Self::Variant(item) - } -} -impl<'lt> From<&'lt ItemKind> for FragmentRef<'lt> { - fn from(item: &'lt ItemKind) -> Self { - Self::ItemKind(item) - } -} -impl<'lt> From<&'lt Item> for FragmentRef<'lt> { - fn from(item: &'lt Item) -> Self { - Self::Item(item) - } + $( + impl From<$ty> for Fragment { + fn from(fragment: $ty) -> Self { + Self::$ty(fragment) + } + } + impl<'lt> From<&'lt $ty> for FragmentRef<'lt> { + fn from(fragment: &'lt $ty) -> Self { + Self::$ty(fragment) + } + } + )* + }; } -/// A borrowed fragment of the AST: this enumeration can represent any node in the AST. -#[derive(Copy)] -#[derive_group_for_ast_base] -#[allow(missing_docs)] -pub enum FragmentRef<'lt> { - GenericValue(&'lt GenericValue), - PrimitiveTy(&'lt PrimitiveTy), - Region(&'lt Region), - Ty(&'lt Ty), - DynTraitGoal(&'lt DynTraitGoal), - Metadata(&'lt Metadata), - Expr(&'lt Expr), - Pat(&'lt Pat), - Arm(&'lt Arm), - Guard(&'lt Guard), - BorrowKind(&'lt BorrowKind), - BindingMode(&'lt BindingMode), - PatKind(&'lt PatKind), - GuardKind(&'lt GuardKind), - Lhs(&'lt Lhs), - ImplExpr(&'lt ImplExpr), - ImplExprKind(&'lt ImplExprKind), - ImplItem(&'lt ImplItem), - ImplItemKind(&'lt ImplItemKind), - TraitItem(&'lt TraitItem), - TraitItemKind(&'lt TraitItemKind), - QuoteContent(&'lt QuoteContent), - Quote(&'lt Quote), - ItemQuoteOrigin(&'lt ItemQuoteOrigin), - ItemQuoteOriginKind(&'lt ItemQuoteOriginKind), - ItemQuoteOriginPosition(&'lt ItemQuoteOriginPosition), - LoopKind(&'lt LoopKind), - ControlFlowKind(&'lt ControlFlowKind), - LoopState(&'lt LoopState), - ExprKind(&'lt ExprKind), - GenericParamKind(&'lt GenericParamKind), - TraitGoal(&'lt TraitGoal), - ImplIdent(&'lt ImplIdent), - ProjectionPredicate(&'lt ProjectionPredicate), - GenericConstraint(&'lt GenericConstraint), - GenericParam(&'lt GenericParam), - Generics(&'lt Generics), - SafetyKind(&'lt SafetyKind), - Attribute(&'lt Attribute), - AttributeKind(&'lt AttributeKind), - DocCommentKind(&'lt DocCommentKind), - SpannedTy(&'lt SpannedTy), - Param(&'lt Param), - Variant(&'lt Variant), - ItemKind(&'lt ItemKind), - Item(&'lt Item), -} +mk!( + GlobalId, + Expr, + Pat, + ExprKind, + PatKind, + Ty, + TyKind, + Metadata, + Literal, + LocalId, + Lhs, + Symbol, + LoopKind, + SafetyKind, + Quote, + SpannedTy, + BindingMode, + PrimitiveTy, + Region, + ImplExpr, + IntKind, + FloatKind, + GenericValue, + Arm, + LoopState, + ControlFlowKind, + DynTraitGoal, + Attribute, + QuoteContent, + BorrowKind, + TraitGoal, + ImplExprKind, + IntSize, + Signedness, + Guard, + AttributeKind, + GuardKind, + ImplItem, + ImplItemKind, + TraitItem, + TraitItemKind, + ItemQuoteOrigin, + ItemQuoteOriginKind, + ItemQuoteOriginPosition, + GenericParamKind, + ImplIdent, + ProjectionPredicate, + GenericParam, + Generics, + DocCommentKind, + Param, + Variant, + ItemKind, + Item, + GenericConstraint, + ErrorNode, + Module, + ResugaredExprKind, + ResugaredTyKind, + ResugaredPatKind, + ResugaredImplItemKind, + ResugaredTraitItemKind, + ResugaredItemKind +); From b03673a8c3723a8d2b4d8dc56c301ae907fa803d Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 17:37:43 +0200 Subject: [PATCH 02/18] feat(rengine/macros): intro. partial_apply --- rust-engine/macros/src/lib.rs | 16 +++++++ rust-engine/macros/src/partial_application.rs | 42 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 rust-engine/macros/src/partial_application.rs diff --git a/rust-engine/macros/src/lib.rs b/rust-engine/macros/src/lib.rs index 1b7b419ca..1684ad87b 100644 --- a/rust-engine/macros/src/lib.rs +++ b/rust-engine/macros/src/lib.rs @@ -16,6 +16,7 @@ use syn::{ }; use utils::*; +mod partial_application; mod replace; mod utils { @@ -175,3 +176,18 @@ pub fn setup_error_handling_struct(_attr: TokenStream, item: TokenStream) -> Tok pub fn replace(attr: TokenStream, item: TokenStream) -> TokenStream { replace::replace(attr, item) } + +/// An attribute procedural macro that creates a new `macro_rules!` definition +/// by partially applying an existing macro or function with a given token stream. +/// +/// Usage: +/// ```rust,ignore +/// #[partial_apply(original_macro!, my_expression,)] +/// macro_rules! new_proxy_macro { +/// // This content is ignored and replaced by the proc macro. +/// } +/// ``` +#[proc_macro_attribute] +pub fn partial_apply(attr: TokenStream, item: TokenStream) -> TokenStream { + partial_application::partial_apply(attr, item) +} diff --git a/rust-engine/macros/src/partial_application.rs b/rust-engine/macros/src/partial_application.rs new file mode 100644 index 000000000..059c6cbd8 --- /dev/null +++ b/rust-engine/macros/src/partial_application.rs @@ -0,0 +1,42 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{ExprPath, Token, parse_macro_input}; + +struct PartialApplyArgs { + ident: ExprPath, + bang: Option, + prefix: proc_macro2::TokenStream, +} + +impl syn::parse::Parse for PartialApplyArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident = input.parse()?; + let bang = input.parse()?; + input.parse::()?; + Ok(PartialApplyArgs { + ident, + bang, + prefix: input.parse()?, + }) + } +} + +pub(crate) fn partial_apply(attr: TokenStream, item: TokenStream) -> TokenStream { + let PartialApplyArgs { + ident, + bang, + prefix, + } = parse_macro_input!(attr as PartialApplyArgs); + let input_macro = parse_macro_input!(item as syn::ItemMacro); + let macro_name = input_macro.ident; + let attrs = input_macro.attrs; + quote! { + #(#attrs)* + macro_rules! #macro_name { + ($($rest:tt)*) => { + #ident #bang(#prefix $($rest)*) + }; + } + } + .into() +} From 3639e1cc0040fe5d3d84e7b88a0ce062482c1fb9 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 17:38:14 +0200 Subject: [PATCH 03/18] feat(rengine/macros): intro. `prepend_associated_functions_with` --- rust-engine/macros/src/lib.rs | 43 ++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/rust-engine/macros/src/lib.rs b/rust-engine/macros/src/lib.rs index 1684ad87b..f9f97ed57 100644 --- a/rust-engine/macros/src/lib.rs +++ b/rust-engine/macros/src/lib.rs @@ -12,7 +12,7 @@ use proc_macro2::{Group, Ident, Span}; use quote::{ToTokens, quote}; use syn::{ Field, FieldsUnnamed, Token, parse_macro_input, parse_quote, punctuated::Punctuated, - token::Paren, + token::Paren, visit_mut::VisitMut, }; use utils::*; @@ -191,3 +191,44 @@ pub fn replace(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn partial_apply(attr: TokenStream, item: TokenStream) -> TokenStream { partial_application::partial_apply(attr, item) } + +/// Prepend the body any associated function with the given attribute payload. +/// ```rust,ignore +/// #[prepend_associated_functions_with(println!("self is {self}");)] +/// impl Foo { +/// fn f(self) {} +/// } +/// ``` +/// +/// Expands to: +/// ```rust,ignore +/// impl Foo { +/// fn f(self) { +/// println!("self is {self}"); +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn prepend_associated_functions_with(attr: TokenStream, item: TokenStream) -> TokenStream { + struct Visitor { + prefix: syn::Expr, + } + impl VisitMut for Visitor { + fn visit_item_impl_mut(&mut self, impl_block: &mut syn::ItemImpl) { + // let mut impl_block: syn::ItemImpl = parse_macro_input!(item); + for item in &mut impl_block.items { + let syn::ImplItem::Fn(impl_item_fn) = item else { + continue; + }; + impl_item_fn.block.stmts.insert( + 0, + syn::Stmt::Expr(self.prefix.clone(), Some(Token![;](Span::mixed_site()))), + ); + } + } + } + let mut item: syn::Item = parse_macro_input!(item); + let prefix = parse_macro_input!(attr); + Visitor { prefix }.visit_item_mut(&mut item); + quote! {#item}.into() +} From cd66ec33bad0ed59af7ae54413c07645ddc238bc Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 17:41:53 +0200 Subject: [PATCH 04/18] feat(rengine): enhance and fix `Print` trait --- rust-engine/src/printer.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/rust-engine/src/printer.rs b/rust-engine/src/printer.rs index 28211e79f..a7dd55849 100644 --- a/rust-engine/src/printer.rs +++ b/rust-engine/src/printer.rs @@ -1,6 +1,8 @@ //! This modules provides types and helper for the printers of hax. mod allocator; +use std::ops::Deref; + pub use allocator::Allocator; use crate::ast; @@ -19,6 +21,18 @@ pub trait Resugaring: for<'a> dyn_compatible::AstVisitorMut<'a> { pub trait Printer: Sized { /// A list of resugaring phases. fn resugaring_phases() -> Vec>; + /// The name of the printer + const NAME: &str; +} + +/// Implement `Printer` for `Allocator

` when `P` implements `Printer`. +/// This is just a convenience implementation proxying the underlying `Printer` implementation for `P`. +impl Printer for Allocator

{ + fn resugaring_phases() -> Vec> { + P::resugaring_phases() + } + + const NAME: &str = P::NAME; } /// Placeholder type for sourcemaps. @@ -27,24 +41,22 @@ pub struct SourceMap; /// Helper trait to print AST fragments. pub trait Print: Printer { /// Print a fragment using a backend. - fn print(self, fragment: &mut T) -> Option<(String, SourceMap)>; + fn print(self, fragment: T) -> (String, SourceMap); } -impl Print - for P +impl Print for P where for<'a, 'b> &'b T: Pretty<'a, Allocator, ast::span::Span>, // The following node is equivalent to "T is an AST node" for<'a> dyn Resugaring: dyn_compatible::AstVisitableMut<'a, T>, { - fn print(self, fragment: &mut T) -> Option<(String, SourceMap)> { + fn print(self, mut fragment: T) -> (String, SourceMap) { for mut reguaring_phase in Self::resugaring_phases() { - reguaring_phase.visit(fragment) + reguaring_phase.visit(&mut fragment) } let allocator = Allocator::new(self); - let doc = fragment.pretty(&allocator).into_doc(); - let mut mem = Vec::new(); - doc.render(80, &mut mem).ok()?; - Some((str::from_utf8(&mem).ok()?.to_string(), SourceMap)) + let doc_builder: pretty::BoxDoc<'_, ast::span::Span> = + fragment.pretty(&allocator).into_doc(); + (doc_builder.deref().pretty(80).to_string(), SourceMap) } } From d741169d9598954aca6922e9aefac6058ea951da Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 17:44:14 +0200 Subject: [PATCH 05/18] feat(rengine): intro. trait `PrettyAst` --- rust-engine/Cargo.toml | 1 + rust-engine/src/printer.rs | 3 + rust-engine/src/printer/pretty_ast.rs | 247 ++++++++++++++++++++++++++ 3 files changed, 251 insertions(+) create mode 100644 rust-engine/src/printer/pretty_ast.rs diff --git a/rust-engine/Cargo.toml b/rust-engine/Cargo.toml index e8a67b714..6a1d3296b 100644 --- a/rust-engine/Cargo.toml +++ b/rust-engine/Cargo.toml @@ -20,3 +20,4 @@ serde_stacker = "0.1.12" pretty = "0.12" itertools.workspace = true derive_generic_visitor = { git = "https://github.com/W95Psp/derive-generic-visitor.git", branch = "visitable-group-add-attrs" } +pastey = "0.1.0" diff --git a/rust-engine/src/printer.rs b/rust-engine/src/printer.rs index a7dd55849..6fa345552 100644 --- a/rust-engine/src/printer.rs +++ b/rust-engine/src/printer.rs @@ -9,6 +9,9 @@ use crate::ast; use ast::visitors::dyn_compatible; use pretty::Pretty; +pub mod pretty_ast; +pub use pretty_ast::PrettyAst; + /// A resugaring is an erased mapper visitor with a name. /// A resugaring is a *local* transformation on the AST that produces exclusively `ast::resugared` nodes. /// Any involved or non-local transformation should be a phase, not a resugaring. diff --git a/rust-engine/src/printer/pretty_ast.rs b/rust-engine/src/printer/pretty_ast.rs new file mode 100644 index 000000000..a27bb70e8 --- /dev/null +++ b/rust-engine/src/printer/pretty_ast.rs @@ -0,0 +1,247 @@ +//! This module provides the trait [`PrettyAst`] which is the main trait a printer should implement. +//! The module also provides a set of macros and helpers. + +use std::fmt::Display; + +use super::*; +use crate::ast::*; +use pretty::{DocAllocator, DocBuilder}; + +use crate::symbol::Symbol; +use identifiers::*; +use literals::*; +use resugared::*; + +/// A newtype for any value that serde can serialize. When formatted, this +/// renders to a convenient `just` command line invocation to see the full +/// value, serialized as JSON. +pub struct DebugJSON(T); + +impl Display for DebugJSON { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn append_line_json(path: &str, value: &serde_json::Value) -> std::io::Result { + use std::{ + fs::OpenOptions, + io::{BufRead, BufReader, Write}, + }; + let file = OpenOptions::new() + .read(true) + .append(true) + .create(true) + .open(path)?; + let count = BufReader::new(&file).lines().count(); + writeln!(&file, "{}", value)?; + Ok(count) + } + + const PATH: &str = "/tmp/hax-ast-debug.json"; + let id = append_line_json(PATH, &serde_json::to_value(&self.0).unwrap()).unwrap(); + write!(f, "`just debug-json {id}`") + } +} + +impl<'a, 'b, A: 'a + Clone, P: PrettyAst<'a, 'b, A>, T: 'b + serde::Serialize> Pretty<'a, P, A> + for DebugJSON +{ + fn pretty(self, allocator: &'a P) -> DocBuilder<'a, P, A> { + allocator.as_string(format!("{}", self)) + } +} + +#[macro_export] +/// Similar to [`std::todo`], but returns a document instead of panicking with a message. +macro_rules! todo_document { + ($allocator:ident,) => { + {return $allocator.todo_document(&format!("TODO_LINE_{}", std::line!()));} + }; + ($allocator:ident, $($tt:tt)*) => { + { + let message = format!($($tt)*); + return $allocator.todo_document(&message); + } + }; +} +pub use todo_document; + +#[macro_export] +/// `install_pretty_helpers!(allocator: AllocatorType)` defines locally the helpers macros and functions using allocator `allocator`. +macro_rules! install_pretty_helpers { + ($allocator:ident : $allocator_type:ty) => { + /// `opt!(e)` is a shorthand for `e.as_ref().map(|e| docs![e])` + #[allow(unused)] + macro_rules! opt { + ($e:expr) => {$e.as_ref().map(|e| <_ as pretty::Pretty<'_, $allocator_type, _>>::pretty(e, $allocator))}; + } + + /// `iter_pretty(e)` is a shorthand for `e.iter().map(|e| docs![e])` + #[allow(unused)] + macro_rules! iter_pretty { + ($e:expr) => {$e.iter().map(|el| el.pretty($allocator))}; + } + + $crate::printer::pretty_ast::install_pretty_helpers!( + @$allocator, + #[doc = ::std::concat!("Proxy macro for [`", stringify!($crate), "::printer::pretty_ast::todo_document`] that automatically uses `", stringify!($allocator),"` as allocator.")] + disambiguated_todo{$crate::printer::pretty_ast::todo_document!}, + #[doc = ::std::concat!("Proxy macro for [`pretty::docs`] that automatically uses `", stringify!($allocator),"` as allocator.")] + docs{pretty::docs!}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::nil`] that automatically uses `", stringify!($allocator),"` as allocator.")] + nil{<$allocator_type as ::pretty::DocAllocator<'_, _>>::nil}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::fail`] that automatically uses `", stringify!($allocator),"` as allocator.")] + fail{<$allocator_type as ::pretty::DocAllocator<'_, _>>::fail}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::hardline`] that automatically uses `", stringify!($allocator),"` as allocator.")] + hardline{<$allocator_type as ::pretty::DocAllocator<'_, _>>::hardline}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::space`] that automatically uses `", stringify!($allocator),"` as allocator.")] + space{<$allocator_type as ::pretty::DocAllocator<'_, _>>::space}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::line`] that automatically uses `", stringify!($allocator),"` as allocator.")] + disambiguated_line{<$allocator_type as ::pretty::DocAllocator<'_, _>>::line}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::line_`] that automatically uses `", stringify!($allocator),"` as allocator.")] + line_{<$allocator_type as ::pretty::DocAllocator<'_, _>>::line_}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::softline`] that automatically uses `", stringify!($allocator),"` as allocator.")] + softline{<$allocator_type as ::pretty::DocAllocator<'_, _>>::softline}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::softline_`] that automatically uses `", stringify!($allocator),"` as allocator.")] + softline_{<$allocator_type as ::pretty::DocAllocator<'_, _>>::softline_}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::as_string`] that automatically uses `", stringify!($allocator),"` as allocator.")] + as_string{<$allocator_type as ::pretty::DocAllocator<'_, _>>::as_string}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::text`] that automatically uses `", stringify!($allocator),"` as allocator.")] + text{<$allocator_type as ::pretty::DocAllocator<'_, _>>::text}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::concat`] that automatically uses `", stringify!($allocator),"` as allocator.")] + disambiguated_concat{<$allocator_type as ::pretty::DocAllocator<'_, _>>::concat}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::intersperse`] that automatically uses `", stringify!($allocator),"` as allocator.")] + intersperse{<$allocator_type as ::pretty::DocAllocator<'_, _>>::intersperse}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::column`] that automatically uses `", stringify!($allocator),"` as allocator.")] + column{<$allocator_type as ::pretty::DocAllocator<'_, _>>::column}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::nesting`] that automatically uses `", stringify!($allocator),"` as allocator.")] + nesting{<$allocator_type as ::pretty::DocAllocator<'_, _>>::nesting}, + #[doc = ::std::concat!("Proxy macro for [`pretty::DocAllocator::reflow`] that automatically uses `", stringify!($allocator),"` as allocator.")] + reflow{<$allocator_type as ::pretty::DocAllocator<'_, _>>::reflow} + ); + }; + (@$allocator:ident, $($(#[$($attrs:tt)*])*$name:ident{$($callable:tt)*}),*) => { + $( + #[hax_rust_engine_macros::partial_apply($($callable)*, $allocator,)] + #[allow(unused)] + $(#[$($attrs)*])* + macro_rules! $name {} + )* + }; +} +pub use install_pretty_helpers; + +macro_rules! mk { + ($($ty:ident),*) => { + pastey::paste! { + /// A trait that defines a print method per type in the AST. + /// This is the main trait a printer should implement. + /// This trait takes care automatically of annotations (think source maps). + pub trait PrettyAst<'a, 'b, A: 'a + Clone>: DocAllocator<'a, A> + Printer { + /// Produces a todo document. + fn todo_document(&'a self, message: &str) -> DocBuilder<'a, Self, A> { + self.as_string(message) + } + /// Produces an error document for an unimplemented document. + fn unimplemented_method(&'a self, method: &str, ast: ast::fragment::FragmentRef<'_>) -> DocBuilder<'a, Self, A> { + self.text(format!("`{method}` unimpl, {}", DebugJSON(ast))).parens() + } + $( + #[doc = "Defines how a printer prints a given type."] + #[doc = "Don't call this method directly, instead use `Pretty::pretty`."] + #[doc = "Calling this method directly will cause issues related to spans or syntax highlighting."] + #[deprecated = concat!("Please use `Pretty::pretty` instead: the method `", stringify!([<$ty:snake>]), "` should never be called directly.")] + fn [<$ty:snake>](&'a self, [<$ty:snake>]: &'b $ty) -> DocBuilder<'a, Self, A> { + mk!(@method_body $ty [<$ty:snake>] self [<$ty:snake>]) + } + )* + } + + $( + impl<'a, 'b, A: 'a + Clone, P: PrettyAst<'a, 'b, A>> Pretty<'a, P, A> for &'b $ty { + fn pretty(self, allocator: &'a P) -> DocBuilder<'a, P, A> { + // Note about deprecation: + // Here is the only place where calling the deprecated methods from the trait `PrettyAst` is fine. + // Here is the place we (will) take care of spans, etc. + #[allow(deprecated)] + let print =

>::[<$ty:snake>]; + print(allocator, self) + } + } + )* + } + }; + // Special default implementation for specific types + (@method_body Symbol $meth:ident $self:ident $value:ident) => { + $self.text($value.to_string()) + }; + (@method_body LocalId $meth:ident $self:ident $value:ident) => { + ::pretty::docs![$self, &$value.0] + }; + (@method_body $ty:ident $meth:ident $self:ident $value:ident) => { + $self.unimplemented_method(stringify!($meth), ast::fragment::FragmentRef::from($meth)) + }; +} + +mk!( + GlobalId, + Expr, + Pat, + ExprKind, + PatKind, + Ty, + TyKind, + Metadata, + Literal, + LocalId, + Lhs, + Symbol, + LoopKind, + SafetyKind, + Quote, + SpannedTy, + BindingMode, + PrimitiveTy, + Region, + ImplExpr, + IntKind, + FloatKind, + GenericValue, + Arm, + LoopState, + ControlFlowKind, + DynTraitGoal, + Attribute, + QuoteContent, + BorrowKind, + TraitGoal, + ImplExprKind, + IntSize, + Signedness, + Guard, + AttributeKind, + GuardKind, + ImplItem, + ImplItemKind, + TraitItem, + TraitItemKind, + ItemQuoteOrigin, + ItemQuoteOriginKind, + ItemQuoteOriginPosition, + GenericParamKind, + ImplIdent, + ProjectionPredicate, + GenericParam, + Generics, + DocCommentKind, + Param, + Variant, + ItemKind, + Item, + GenericConstraint, + ErrorNode, + Module, + ResugaredExprKind, + ResugaredTyKind, + ResugaredPatKind, + ResugaredImplItemKind, + ResugaredTraitItemKind, + ResugaredItemKind +); From 46237cd0566c8b9b10f994d74d9c43d7d3158d02 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 17:48:03 +0200 Subject: [PATCH 06/18] fix(ocaml_of_json_schema): escape `module` --- engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js b/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js index 1d9e7c001..193e0172d 100644 --- a/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js +++ b/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js @@ -74,7 +74,7 @@ let variantNameOf = s => { return v + "'"; return v; }; -let typeNameOf = s => s.replace(/[A-Z]/g, (l, i) => `${i ? '_' : ''}${l.toLowerCase()}`); +let typeNameOf = s => s.replace(/[A-Z]/g, (l, i) => `${i ? '_' : ''}${l.toLowerCase()}`).replace(/^module$/, "module'"); let fieldNameOf = s => { let ocaml_keywords = ["and", "as", "assert", "asr", "begin", "class", "constraint", "do", "done", "downto", "else", "end", "exception", "external", From a526066e026333c107c789063575da8dffb8f478 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 17:48:23 +0200 Subject: [PATCH 07/18] chore: update lockfile --- Cargo.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 647d4af18..86b3d4121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,6 +670,7 @@ dependencies = [ "hax-rust-engine-macros", "hax-types", "itertools 0.11.0", + "pastey", "pretty", "schemars", "serde", @@ -1111,6 +1112,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "path-clean" version = "1.0.1" From 7f22dbb7eb9f92236c36311d4c888ac47c9fd8b4 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 18:17:31 +0200 Subject: [PATCH 08/18] fix(rengine): fix some clippy warnings --- rust-engine/src/ast/visitors.rs | 5 ++--- rust-engine/src/lib.rs | 1 + rust-engine/src/printer/pretty_ast.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust-engine/src/ast/visitors.rs b/rust-engine/src/ast/visitors.rs index 4e515f64c..503083c72 100644 --- a/rust-engine/src/ast/visitors.rs +++ b/rust-engine/src/ast/visitors.rs @@ -7,8 +7,7 @@ //! //! We provide four main visitors. //! - [`AstVisitor`] and [`AstVisitorMut`]: visitor that never early exit. -//! - [`AstEarlyExitVisitor`] and [`AstEarlyExitVisitorMut`]: visitor that can -//! early exit. +//! - [`AstEarlyExitVisitor`] and [`AstEarlyExitVisitorMut`]: visitor that can early exit. //! //! Each trait provides methods `visit_expr`, `visit_ty`, etc. enabling easy AST //! traversal. @@ -403,7 +402,7 @@ mod replaced { } pub use replaced::dyn_compatible; -pub(self) use replaced::{fallible, infallible}; +use replaced::{fallible, infallible}; pub use fallible::{ AstEarlyExitVisitor, AstEarlyExitVisitorMut, AstVisitable as AstVisitableFallible, diff --git a/rust-engine/src/lib.rs b/rust-engine/src/lib.rs index d4548a1d0..ff7a5b2cb 100644 --- a/rust-engine/src/lib.rs +++ b/rust-engine/src/lib.rs @@ -9,6 +9,7 @@ )] pub mod ast; +pub mod backends; pub mod hax_io; pub mod lean; pub mod names; diff --git a/rust-engine/src/printer/pretty_ast.rs b/rust-engine/src/printer/pretty_ast.rs index a27bb70e8..8fd907670 100644 --- a/rust-engine/src/printer/pretty_ast.rs +++ b/rust-engine/src/printer/pretty_ast.rs @@ -30,7 +30,7 @@ impl Display for DebugJSON { .create(true) .open(path)?; let count = BufReader::new(&file).lines().count(); - writeln!(&file, "{}", value)?; + writeln!(&file, "{value}")?; Ok(count) } @@ -44,7 +44,7 @@ impl<'a, 'b, A: 'a + Clone, P: PrettyAst<'a, 'b, A>, T: 'b + serde::Serialize> P for DebugJSON { fn pretty(self, allocator: &'a P) -> DocBuilder<'a, P, A> { - allocator.as_string(format!("{}", self)) + allocator.as_string(format!("{self}")) } } From af84fbcb44148efc8b6a0423fc9fcf7deda1fa6e Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Wed, 13 Aug 2025 18:24:02 +0200 Subject: [PATCH 09/18] feat(rengine): introduce module backend --- rust-engine/src/backends.rs | 13 +++++++ rust-engine/src/backends/rust.rs | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 rust-engine/src/backends.rs create mode 100644 rust-engine/src/backends/rust.rs diff --git a/rust-engine/src/backends.rs b/rust-engine/src/backends.rs new file mode 100644 index 000000000..bcce7eb2a --- /dev/null +++ b/rust-engine/src/backends.rs @@ -0,0 +1,13 @@ +//! Backends + +pub mod rust; + +#[allow(unused)] +mod prelude { + pub use crate::ast::*; + pub use crate::printer::*; + pub use hax_rust_engine_macros::prepend_associated_functions_with; + pub use pretty::DocAllocator; + pub use pretty::Pretty; + pub use pretty_ast::install_pretty_helpers; +} diff --git a/rust-engine/src/backends/rust.rs b/rust-engine/src/backends/rust.rs new file mode 100644 index 000000000..81c138992 --- /dev/null +++ b/rust-engine/src/backends/rust.rs @@ -0,0 +1,64 @@ +//! A Rust backend for hax. +//! Note: for now, this contains only a minimal skeleton of Rust printer, which serves solely as an example printer. + +use super::prelude::*; + +/// The Rust backend and printer. +pub struct Rust; + +impl Printer for Rust { + fn resugaring_phases() -> Vec> { + vec![] + } + + const NAME: &str = "Rust"; +} + +const INDENT: isize = 4; + +#[prepend_associated_functions_with(install_pretty_helpers!(self: Self))] +// Note: the `const` wrapping makes my IDE and LSP happy. Otherwise, I don't get +// autocompletion of methods in the impl block below. +const _: () = { + // Boilerplate: define local macros to disambiguate otherwise `std` macros. + #[allow(unused)] + macro_rules! todo {($($tt:tt)*) => {disambiguated_todo!($($tt)*)};} + #[allow(unused)] + macro_rules! line {($($tt:tt)*) => {disambiguated_line!($($tt)*)};} + #[allow(unused)] + macro_rules! concat {($($tt:tt)*) => {disambiguated_concat!($($tt)*)};} + + impl<'a, 'b, A: 'a + Clone> PrettyAst<'a, 'b, A> for Allocator { + fn module(&'a self, module: &'b Module) -> pretty::DocBuilder<'a, Self, A> { + intersperse!(iter_pretty!(module.items), docs![hardline!(), hardline!()]) + } + fn item(&'a self, item: &'b Item) -> pretty::DocBuilder<'a, Self, A> { + docs![&item.meta, item.kind()] + } + fn item_kind(&'a self, item_kind: &'b ItemKind) -> pretty::DocBuilder<'a, Self, A> { + match item_kind { + ItemKind::Fn { + name, + generics: _, + body, + params, + safety, + } => { + docs![ + safety, + text!("fn"), + space!(), + name, + intersperse!(iter_pretty!(params), docs![",", line!()]) + .enclose(line_!(), line_!()) + .nest(INDENT) + .parens() + .group(), + docs![line_!(), body, line_!(),].nest(INDENT).braces() + ] + } + _ => todo!(), + } + } + } +}; From 04a355541492a8e61280497fa8f277d8fea3fd5f Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 09:53:00 +0200 Subject: [PATCH 10/18] doc(rengine): add more doc --- rust-engine/src/ast/fragment.rs | 28 +++++- rust-engine/src/backends.rs | 19 +++- rust-engine/src/printer.rs | 22 ++++- rust-engine/src/printer/pretty_ast.rs | 132 +++++++++++++++++++++++--- 4 files changed, 179 insertions(+), 22 deletions(-) diff --git a/rust-engine/src/ast/fragment.rs b/rust-engine/src/ast/fragment.rs index 589df8196..addb4f48d 100644 --- a/rust-engine/src/ast/fragment.rs +++ b/rust-engine/src/ast/fragment.rs @@ -1,11 +1,31 @@ -//! The `Fragment` type for holding arbitrary AST fragments. +//! Enumeration types of any possible fragment of AST (`Fragment` / `FragmentRef`). //! -//! This enum is useful for diagnostics or dynamic dispatch on generic AST values. -//! It acts as a type-erased wrapper around various core AST node types. +//! Many components (diagnostics, logging, printers) want to refer to “some AST +//! node” without knowing its concrete type. This module provides: +//! - [`Fragment`]: an **owned** enum covering core AST node types. +//! - [`FragmentRef`]: a **borrowed** counterpart. +//! +//! These are handy when implementing generic facilities such as error reporters, +//! debugging helpers, or pretty-printers that need to branch on “what kind of +//! node is this?” at runtime. +//! +//! ## Notes +//! - Both enums are mechanically generated to stay in sync with the canonical +//! AST types. If you add a new core AST node, update the macro invocation at +//! the bottom of this file so `Fragment`/`FragmentRef` learn about it. +//! - The [`Unknown`] variant exists as a last-resort placeholder when a value +//! cannot be represented by a known variant. Prefer concrete variants when +//! possible. use crate::ast::*; -/// Macro that derives automatically the `Fragment` and `FragmentRef` enumerations. +/// The `mk!` macro takes a flat list of AST type identifiers and expands to +/// two enums: +/// - `Fragment` with owned variants (`Foo(Foo)`), and +/// - `FragmentRef<'a>` with borrowed variants (`Foo(&'a Foo)`). +/// +/// The generated enums also implement the obvious `From` conversions, making +/// it ergonomic to wrap concrete AST values as fragments. macro_rules! mk { ($($ty:ident),*) => { #[derive_group_for_ast] diff --git a/rust-engine/src/backends.rs b/rust-engine/src/backends.rs index bcce7eb2a..0fa812231 100644 --- a/rust-engine/src/backends.rs +++ b/rust-engine/src/backends.rs @@ -1,9 +1,26 @@ -//! Backends +//! Code generation backends. +//! +//! A backend turns the hax AST into a concrete textual form: F*, Lean, or any +//! future target. +//! +//! This top-level module is mostly an **index** of available backends and a +//! small **prelude** to make backend modules concise. +//! +//! # Adding a new backend +//! 1. Create a submodule under `src/backends/`, e.g. `foo.rs`. +//! 2. Put your printer + glue code there. +//! 3. Re-export it here with `pub mod foo;`. +//! +//! See [`rust`] for an example implementation. pub mod rust; #[allow(unused)] mod prelude { + //! Small "bring-into-scope" set used by backend modules. + //! + //! Importing this prelude saves repetitive `use` lists in per-backend + //! modules without forcing these names on downstream users. pub use crate::ast::*; pub use crate::printer::*; pub use hax_rust_engine_macros::prepend_associated_functions_with; diff --git a/rust-engine/src/printer.rs b/rust-engine/src/printer.rs index 6fa345552..872e87b82 100644 --- a/rust-engine/src/printer.rs +++ b/rust-engine/src/printer.rs @@ -1,4 +1,13 @@ -//! This modules provides types and helper for the printers of hax. +//! Printer infrastructure: allocators, traits, and the printing pipeline. +//! +//! This module contains the common plumbing that backends and printers rely on +//! to turn AST values into formatted text: +//! - [`Allocator`]: a thin wrapper around the `pretty` crate's allocator, +//! parameterized by the backend, used to produce [`pretty::Doc`] nodes. +//! - [`PrettyAst`]: the trait that printers implement to provide per-type +//! formatting of Hax AST nodes (re-exported from [`pretty_ast`]). +//! - The resugaring pipeline: a sequence of local AST rewrites that make +//! emitted code idiomatic for the target language before pretty-printing. mod allocator; use std::ops::Deref; @@ -15,6 +24,15 @@ pub use pretty_ast::PrettyAst; /// A resugaring is an erased mapper visitor with a name. /// A resugaring is a *local* transformation on the AST that produces exclusively `ast::resugared` nodes. /// Any involved or non-local transformation should be a phase, not a resugaring. +/// +/// Backends may provide **multiple resugaring phases** to incrementally refine +/// the tree into something idiomatic for the target language (e.g., desugaring +/// pattern sugar into a more uniform core, then resugaring back into target +/// idioms). Each phase mutates the AST in place and should be small, focused, +/// and easy to test. +/// +/// If you add a new phase, make sure it appears in the backend’s +/// `resugaring_phases()` list in the correct order. pub trait Resugaring: for<'a> dyn_compatible::AstVisitorMut<'a> { /// Get the name of the resugar. fn name(&self) -> String; @@ -43,7 +61,7 @@ pub struct SourceMap; /// Helper trait to print AST fragments. pub trait Print: Printer { - /// Print a fragment using a backend. + /// Print a single AST fragment using this backend. fn print(self, fragment: T) -> (String, SourceMap); } diff --git a/rust-engine/src/printer/pretty_ast.rs b/rust-engine/src/printer/pretty_ast.rs index 8fd907670..f30e9caac 100644 --- a/rust-engine/src/printer/pretty_ast.rs +++ b/rust-engine/src/printer/pretty_ast.rs @@ -1,5 +1,23 @@ -//! This module provides the trait [`PrettyAst`] which is the main trait a printer should implement. -//! The module also provides a set of macros and helpers. +//! Pretty-printing support for the hax AST. +//! +//! This module defines the trait [`PrettyAst`], which is the **primary trait a printer should +//! implement**. It also exposes a handful of ergonomic helper macros that wire the +//! [`pretty`](https://docs.rs/pretty/) crate's allocator into concise printing code, while +//! taking care of annotations/spans for you. +//! +//! # Quickstart +//! In most printers you: +//! 1. Implement [`Printer`] for your allocator/type, +//! 2. Implement [`PrettyAst`] for that allocator, +//! 3. Call `x.pretty(&allocator)` on AST values. +//! +//! See [`super::backends`] for backend and printer examples. +//! +//! # Lifetimes and Parameters +//! - `'a`: lifetime tied to the **document allocator** (from `pretty::DocAllocator`). +//! - `'b`: lifetime of the **borrowed AST** node(s) being printed. +//! - `A`: the **annotation** type carried in documents (e.g., spans for source maps). +//! `PrettyAst` is generic over `A`. use std::fmt::Display; @@ -12,10 +30,32 @@ use identifiers::*; use literals::*; use resugared::*; -/// A newtype for any value that serde can serialize. When formatted, this -/// renders to a convenient `just` command line invocation to see the full -/// value, serialized as JSON. -pub struct DebugJSON(T); +/// This type is primarily useful inside printer implementations when you want a +/// low-friction way to inspect an AST fragment. +/// +/// # What it does +/// - Appends a JSON representation of the wrapped value to +/// `"/tmp/hax-ast-debug.json"` (one JSON document per line). +/// - Implements [`std::fmt::Display`] to print a `just` invocation you can paste in a shell +/// to re-open that same JSON by line number: +/// `just debug-json ` +/// +/// # Example +/// ```rust +/// # use hax_rust_engine::printer::pretty_ast::DebugJSON; +/// # #[derive(serde::Serialize)] +/// # struct Small { x: u32 } +/// let s = Small { x: 42 }; +/// // Prints something like: `just debug-json 17`. +/// println!("{}", DebugJSON(&s)); +/// // Running `just debug-json 17` will print `{"x":42}` +/// ``` +/// +/// # Notes +/// - This is a **debugging convenience** and intentionally has a side-effect (file write). +/// Avoid keeping it in user-facing output paths. +/// - The file grows over time; occasionally delete it if you no longer need historical entries. +pub struct DebugJSON(pub T); impl Display for DebugJSON { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -64,7 +104,37 @@ macro_rules! todo_document { pub use todo_document; #[macro_export] -/// `install_pretty_helpers!(allocator: AllocatorType)` defines locally the helpers macros and functions using allocator `allocator`. +/// Install pretty-printing helpers partially applied with a given local +/// allocator. +/// +/// This macro declares a set of small, local macros that proxy to the +/// underlying [`pretty::DocAllocator`] methods and macro while capturing your +/// allocator value. It keeps printing code concise and avoids passing the +/// allocator around explicitly. +/// +/// # Syntax +/// ```rust,ignore +/// install_pretty_helpers!(alloc_ident: AllocatorType) +/// ``` +/// +/// - `alloc_ident`: the in-scope variable that implements both +/// [`pretty::DocAllocator`] and [`Printer`]. +/// - `AllocatorType`: the concrete type of that variable. +/// +/// # What gets installed +/// - macro shorthands for common allocator methods: +/// [`pretty::DocAllocator::nil`], [`pretty::DocAllocator::fail`], +/// [`pretty::DocAllocator::hardline`], [`pretty::DocAllocator::space`], +/// [`pretty::DocAllocator::line`], [`pretty::DocAllocator::line_`], +/// [`pretty::DocAllocator::softline`], [`pretty::DocAllocator::softline_`], +/// [`pretty::DocAllocator::as_string`], [`pretty::DocAllocator::text`], +/// [`pretty::DocAllocator::concat`], [`pretty::DocAllocator::intersperse`], +/// [`pretty::DocAllocator::column`], [`pretty::DocAllocator::nesting`], +/// [`pretty::DocAllocator::reflow`]. +/// - a partially applied version of [`pretty::docs!`]. +/// - [`opt!`]: expands `opt!(expr)` to `expr.as_ref().map(|e| e.pretty(alloc_ident))`. +/// - [`iter_pretty!`]: expands to `iter.map(|x| x.pretty(alloc_ident))`. +/// - [`todo_document!`]: produce a placeholder document (that does not panic). macro_rules! install_pretty_helpers { ($allocator:ident : $allocator_type:ty) => { /// `opt!(e)` is a shorthand for `e.as_ref().map(|e| docs![e])` @@ -132,22 +202,54 @@ macro_rules! mk { ($($ty:ident),*) => { pastey::paste! { /// A trait that defines a print method per type in the AST. - /// This is the main trait a printer should implement. - /// This trait takes care automatically of annotations (think source maps). + /// + /// This is the main trait a printer should implement. It ties + /// together: + /// - the [`pretty::DocAllocator`] implementation that builds + /// documents, + /// - the [`Printer`] behavior (syntax highlighting, punctuation + /// helpers, …), + /// - and annotation plumbing (`A`) used for source maps. + /// + /// ## Lifetimes and Type Parameters + /// - `'a`: the allocator/document lifetime. + /// - `'b`: the lifetime of borrowed AST values being printed. + /// - `A`: the annotation type carried by documents (must be + /// `Clone`). + /// + /// ## Implementing `PrettyAst` + /// ```rust,ignore + /// impl<'a, 'b, A: 'a + Clone> PrettyAst<'a, 'b, A> for MyPrinter { } + /// ``` + /// + /// You then implement the actual formatting logic in the generated + /// per-type methods. These methods are intentionally marked + /// `#[deprecated]` to discourage calling them directly; instead, + /// call `node.pretty(self)` from the [`pretty::Pretty`] trait to + /// ensure annotations and spans are applied correctly. + /// + /// Note that using `install_pretty_helpers!` will produce macro + /// that implicitely use `self` as allocator. Take a look at a + /// printer in the [`backends`] module for an example. pub trait PrettyAst<'a, 'b, A: 'a + Clone>: DocAllocator<'a, A> + Printer { - /// Produces a todo document. + /// Produce a non-panicking placeholder document. In general, prefer the use of the helper macro [`todo_document!`]. fn todo_document(&'a self, message: &str) -> DocBuilder<'a, Self, A> { self.as_string(message) } - /// Produces an error document for an unimplemented document. + /// Produce a structured error document for an unimplemented + /// method. + /// + /// Printers may override this for nicer diagnostics (e.g., + /// colored "unimplemented" banners or links back to source + /// locations). The default produces a small, debuggable piece + /// of text that includes the method name and a JSON handle for + /// the AST fragment (via [`DebugJSON`]). fn unimplemented_method(&'a self, method: &str, ast: ast::fragment::FragmentRef<'_>) -> DocBuilder<'a, Self, A> { self.text(format!("`{method}` unimpl, {}", DebugJSON(ast))).parens() } $( - #[doc = "Defines how a printer prints a given type."] - #[doc = "Don't call this method directly, instead use `Pretty::pretty`."] - #[doc = "Calling this method directly will cause issues related to spans or syntax highlighting."] - #[deprecated = concat!("Please use `Pretty::pretty` instead: the method `", stringify!([<$ty:snake>]), "` should never be called directly.")] + #[doc = "Define how the printer formats a value of this AST type."] + #[doc = "⚠️ **Do not call this method directly**. Use [`pretty::Pretty::pretty`] instead, so annotations/spans are preserved correctly."] fn [<$ty:snake>](&'a self, [<$ty:snake>]: &'b $ty) -> DocBuilder<'a, Self, A> { mk!(@method_body $ty [<$ty:snake>] self [<$ty:snake>]) } From 51ef3dd347f6f11eaf84afa995ab4600870914f0 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 13:23:05 +0200 Subject: [PATCH 11/18] fix(ocaml_of_json_schema): make esacping more uniform --- .../ocaml_of_json_schema/ocaml_of_json_schema.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js b/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js index 193e0172d..dd1d27d3d 100644 --- a/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js +++ b/engine/utils/ocaml_of_json_schema/ocaml_of_json_schema.js @@ -74,8 +74,7 @@ let variantNameOf = s => { return v + "'"; return v; }; -let typeNameOf = s => s.replace(/[A-Z]/g, (l, i) => `${i ? '_' : ''}${l.toLowerCase()}`).replace(/^module$/, "module'"); -let fieldNameOf = s => { +let escapeOCamlKeywords = s => { let ocaml_keywords = ["and", "as", "assert", "asr", "begin", "class", "constraint", "do", "done", "downto", "else", "end", "exception", "external", "false", "for", "fun", "function", "functor", "if", "in", @@ -85,10 +84,10 @@ let fieldNameOf = s => { "private", "rec", "sig", "struct", "then", "to", "true", "try", "type", "val", "virtual", "when", "while", "with" ]; - if (ocaml_keywords.includes(s)) - return s + "'"; - return s; -}; + return ocaml_keywords.includes(s) ? s + "'" : s; +} +let typeNameOf = s => escapeOCamlKeywords(s.replace(/[A-Z]/g, (l, i) => `${i ? '_' : ''}${l.toLowerCase()}`)); +let fieldNameOf = s => escapeOCamlKeywords(s); let ensureUnique = (() => { let cache = {}; From 7c1ef7481f12ce89035cc2175bb1ccd2c7955072 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 13:23:34 +0200 Subject: [PATCH 12/18] fix(rengine/macros): drop commented line --- rust-engine/macros/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust-engine/macros/src/lib.rs b/rust-engine/macros/src/lib.rs index f9f97ed57..8066dd4c5 100644 --- a/rust-engine/macros/src/lib.rs +++ b/rust-engine/macros/src/lib.rs @@ -215,7 +215,6 @@ pub fn prepend_associated_functions_with(attr: TokenStream, item: TokenStream) - } impl VisitMut for Visitor { fn visit_item_impl_mut(&mut self, impl_block: &mut syn::ItemImpl) { - // let mut impl_block: syn::ItemImpl = parse_macro_input!(item); for item in &mut impl_block.items { let syn::ImplItem::Fn(impl_item_fn) = item else { continue; From 6124f9daa1ba7c4e607e2f2a89ee81a8981f898a Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 14:47:51 +0200 Subject: [PATCH 13/18] refactor(rengine): factor out list of visitbale AST types --- rust-engine/macros/src/lib.rs | 7 +++ rust-engine/macros/src/replace.rs | 39 +++++++++++++++- rust-engine/src/ast/fragment.rs | 67 +-------------------------- rust-engine/src/ast/visitors.rs | 17 +------ rust-engine/src/printer/pretty_ast.rs | 67 +-------------------------- 5 files changed, 51 insertions(+), 146 deletions(-) diff --git a/rust-engine/macros/src/lib.rs b/rust-engine/macros/src/lib.rs index 8066dd4c5..7d6cd1647 100644 --- a/rust-engine/macros/src/lib.rs +++ b/rust-engine/macros/src/lib.rs @@ -173,6 +173,13 @@ pub fn setup_error_handling_struct(_attr: TokenStream, item: TokenStream) -> Tok #[proc_macro_attribute] /// Replaces all occurrences of an identifier within the attached item. +/// +/// For example, `#[replace(Name => A, B, C)]` will replace `Name` by `A, B, C` +/// in the item the proc-macro is applied on. +/// +/// The special case `#[replace(Name => include(VisitableAstNodes))]` will +/// expand to a list of visitable AST nodes. This is useful in practice, as this +/// list is often repeated. pub fn replace(attr: TokenStream, item: TokenStream) -> TokenStream { replace::replace(attr, item) } diff --git a/rust-engine/macros/src/replace.rs b/rust-engine/macros/src/replace.rs index c5f3022e6..6e885686d 100644 --- a/rust-engine/macros/src/replace.rs +++ b/rust-engine/macros/src/replace.rs @@ -2,9 +2,14 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::{Group, TokenStream as TokenStream2, TokenTree}; +use quote::quote; use syn::parse::{Parse, ParseStream, Result}; use syn::{Ident, Token, parse_macro_input}; +mod kw { + syn::custom_keyword!(include); +} + fn replace_in_stream( stream: TokenStream2, target: &Ident, @@ -37,9 +42,41 @@ impl Parse for AttributeArgs { fn parse(input: ParseStream) -> Result { let target: Ident = input.parse()?; input.parse::]>()?; + let include_clause = |input: ParseStream| -> Result { + input.parse::()?; + let content; + syn::parenthesized!(content in input); + content.parse() + }(input) + .ok(); Ok(AttributeArgs { target, - replacement: input.parse::()?, + replacement: match include_clause { + Some(clause) => match clause.to_string().as_str() { + "VisitableAstNodes" => quote! { + Expr, Pat, ExprKind, PatKind, Ty, TyKind, Metadata, Literal, + LocalId, Lhs, Symbol, LoopKind, SafetyKind, Quote, + SpannedTy, BindingMode, PrimitiveTy, Region, ImplExpr, + IntKind, FloatKind, GenericValue, Arm, LoopState, ControlFlowKind, + DynTraitGoal, Attribute, QuoteContent, BorrowKind, + TraitGoal, ImplExprKind, IntSize, Signedness, Guard, AttributeKind, + GuardKind, ImplItem, ImplItemKind, TraitItem, TraitItemKind, + ItemQuoteOrigin, ItemQuoteOriginKind, ItemQuoteOriginPosition, GenericParamKind, ImplIdent, + ProjectionPredicate, GenericParam, Generics, DocCommentKind, Param, Variant, ItemKind, Item, + GenericConstraint, ErrorNode, Module, + + ResugaredExprKind, ResugaredTyKind, ResugaredPatKind, + ResugaredImplItemKind, ResugaredTraitItemKind, ResugaredItemKind + }.into(), + _ => { + return Err(syn::Error::new_spanned( + clause, + format!("This is not a recognized include pragma."), + )); + } + }, + None => input.parse::()?, + }, }) } } diff --git a/rust-engine/src/ast/fragment.rs b/rust-engine/src/ast/fragment.rs index addb4f48d..2c91e4c4e 100644 --- a/rust-engine/src/ast/fragment.rs +++ b/rust-engine/src/ast/fragment.rs @@ -66,68 +66,5 @@ macro_rules! mk { }; } -mk!( - GlobalId, - Expr, - Pat, - ExprKind, - PatKind, - Ty, - TyKind, - Metadata, - Literal, - LocalId, - Lhs, - Symbol, - LoopKind, - SafetyKind, - Quote, - SpannedTy, - BindingMode, - PrimitiveTy, - Region, - ImplExpr, - IntKind, - FloatKind, - GenericValue, - Arm, - LoopState, - ControlFlowKind, - DynTraitGoal, - Attribute, - QuoteContent, - BorrowKind, - TraitGoal, - ImplExprKind, - IntSize, - Signedness, - Guard, - AttributeKind, - GuardKind, - ImplItem, - ImplItemKind, - TraitItem, - TraitItemKind, - ItemQuoteOrigin, - ItemQuoteOriginKind, - ItemQuoteOriginPosition, - GenericParamKind, - ImplIdent, - ProjectionPredicate, - GenericParam, - Generics, - DocCommentKind, - Param, - Variant, - ItemKind, - Item, - GenericConstraint, - ErrorNode, - Module, - ResugaredExprKind, - ResugaredTyKind, - ResugaredPatKind, - ResugaredImplItemKind, - ResugaredTraitItemKind, - ResugaredItemKind -); +#[hax_rust_engine_macros::replace(AstNodes => include(VisitableAstNodes))] +mk!(GlobalId, AstNodes); diff --git a/rust-engine/src/ast/visitors.rs b/rust-engine/src/ast/visitors.rs index 503083c72..c76339922 100644 --- a/rust-engine/src/ast/visitors.rs +++ b/rust-engine/src/ast/visitors.rs @@ -224,21 +224,7 @@ pub mod wrappers { } } -#[hax_rust_engine_macros::replace(AstNodes => - Expr, Pat, ExprKind, PatKind, Ty, TyKind, Metadata, Literal, - LocalId, Lhs, Symbol, LoopKind, SafetyKind, Quote, - SpannedTy, BindingMode, PrimitiveTy, Region, ImplExpr, - IntKind, FloatKind, GenericValue, Arm, LoopState, ControlFlowKind, - DynTraitGoal, Attribute, QuoteContent, BorrowKind, - TraitGoal, ImplExprKind, IntSize, Signedness, Guard, AttributeKind, - GuardKind, ImplItem, ImplItemKind, TraitItem, TraitItemKind, - ItemQuoteOrigin, ItemQuoteOriginKind, ItemQuoteOriginPosition, GenericParamKind, ImplIdent, - ProjectionPredicate, GenericParam, Generics, DocCommentKind, Param, Variant, ItemKind, Item, - GenericConstraint, ErrorNode, Module, - - ResugaredExprKind, ResugaredTyKind, ResugaredPatKind, - ResugaredImplItemKind, ResugaredTraitItemKind, ResugaredItemKind -)] +#[hax_rust_engine_macros::replace(AstNodes => include(VisitableAstNodes))] mod replaced { use super::*; pub mod infallible { @@ -289,6 +275,7 @@ mod replaced { /// Helper trait to drive visitor. pub trait AstVisitable {} } + #[allow(missing_docs)] pub mod fallible { use super::*; diff --git a/rust-engine/src/printer/pretty_ast.rs b/rust-engine/src/printer/pretty_ast.rs index f30e9caac..cfc801bdb 100644 --- a/rust-engine/src/printer/pretty_ast.rs +++ b/rust-engine/src/printer/pretty_ast.rs @@ -282,68 +282,5 @@ macro_rules! mk { }; } -mk!( - GlobalId, - Expr, - Pat, - ExprKind, - PatKind, - Ty, - TyKind, - Metadata, - Literal, - LocalId, - Lhs, - Symbol, - LoopKind, - SafetyKind, - Quote, - SpannedTy, - BindingMode, - PrimitiveTy, - Region, - ImplExpr, - IntKind, - FloatKind, - GenericValue, - Arm, - LoopState, - ControlFlowKind, - DynTraitGoal, - Attribute, - QuoteContent, - BorrowKind, - TraitGoal, - ImplExprKind, - IntSize, - Signedness, - Guard, - AttributeKind, - GuardKind, - ImplItem, - ImplItemKind, - TraitItem, - TraitItemKind, - ItemQuoteOrigin, - ItemQuoteOriginKind, - ItemQuoteOriginPosition, - GenericParamKind, - ImplIdent, - ProjectionPredicate, - GenericParam, - Generics, - DocCommentKind, - Param, - Variant, - ItemKind, - Item, - GenericConstraint, - ErrorNode, - Module, - ResugaredExprKind, - ResugaredTyKind, - ResugaredPatKind, - ResugaredImplItemKind, - ResugaredTraitItemKind, - ResugaredItemKind -); +#[hax_rust_engine_macros::replace(AstNodes => include(VisitableAstNodes))] +mk!(GlobalId, AstNodes); From bd6af666cba01805e7b7d67d118ca6d3aa61fce8 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 14:51:36 +0200 Subject: [PATCH 14/18] doc(rengine/macros): partial_apply --- rust-engine/macros/src/partial_application.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust-engine/macros/src/partial_application.rs b/rust-engine/macros/src/partial_application.rs index 059c6cbd8..14f4b83e5 100644 --- a/rust-engine/macros/src/partial_application.rs +++ b/rust-engine/macros/src/partial_application.rs @@ -21,6 +21,7 @@ impl syn::parse::Parse for PartialApplyArgs { } } +/// See [`super::partial_apply`]. pub(crate) fn partial_apply(attr: TokenStream, item: TokenStream) -> TokenStream { let PartialApplyArgs { ident, From 229aef34d82b2103cc0ac182caf5f472997e17f1 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 14:55:58 +0200 Subject: [PATCH 15/18] doc(rengine): explicit differences between backends and printers --- rust-engine/src/backends.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rust-engine/src/backends.rs b/rust-engine/src/backends.rs index 0fa812231..cdcfe4139 100644 --- a/rust-engine/src/backends.rs +++ b/rust-engine/src/backends.rs @@ -1,14 +1,15 @@ //! Code generation backends. //! -//! A backend turns the hax AST into a concrete textual form: F*, Lean, or any -//! future target. +//! A backend is consititued of: +//! - a list of AST transformations to apply, those are called phases. +//! - and a printer. //! -//! This top-level module is mostly an **index** of available backends and a -//! small **prelude** to make backend modules concise. +//! This top-level module is mostly an index of available backends and a +//! small prelude to make backend modules concise. //! //! # Adding a new backend //! 1. Create a submodule under `src/backends/`, e.g. `foo.rs`. -//! 2. Put your printer + glue code there. +//! 2. Put your printer and backend there. //! 3. Re-export it here with `pub mod foo;`. //! //! See [`rust`] for an example implementation. From 77053bf78725ea086feb07d1615858d756f38828 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 14:57:40 +0200 Subject: [PATCH 16/18] misc(rengine): drop `opt!` --- rust-engine/src/printer/pretty_ast.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/rust-engine/src/printer/pretty_ast.rs b/rust-engine/src/printer/pretty_ast.rs index cfc801bdb..0975f339f 100644 --- a/rust-engine/src/printer/pretty_ast.rs +++ b/rust-engine/src/printer/pretty_ast.rs @@ -132,17 +132,10 @@ pub use todo_document; /// [`pretty::DocAllocator::column`], [`pretty::DocAllocator::nesting`], /// [`pretty::DocAllocator::reflow`]. /// - a partially applied version of [`pretty::docs!`]. -/// - [`opt!`]: expands `opt!(expr)` to `expr.as_ref().map(|e| e.pretty(alloc_ident))`. /// - [`iter_pretty!`]: expands to `iter.map(|x| x.pretty(alloc_ident))`. /// - [`todo_document!`]: produce a placeholder document (that does not panic). macro_rules! install_pretty_helpers { ($allocator:ident : $allocator_type:ty) => { - /// `opt!(e)` is a shorthand for `e.as_ref().map(|e| docs![e])` - #[allow(unused)] - macro_rules! opt { - ($e:expr) => {$e.as_ref().map(|e| <_ as pretty::Pretty<'_, $allocator_type, _>>::pretty(e, $allocator))}; - } - /// `iter_pretty(e)` is a shorthand for `e.iter().map(|e| docs![e])` #[allow(unused)] macro_rules! iter_pretty { From 643423e2c5d8fdf59c31cf205a8e2bcb97856514 Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 15:16:07 +0200 Subject: [PATCH 17/18] fix(rengine): drop `pretty_iter!` --- rust-engine/src/backends/rust.rs | 4 ++-- rust-engine/src/printer/pretty_ast.rs | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/rust-engine/src/backends/rust.rs b/rust-engine/src/backends/rust.rs index 81c138992..5162989ae 100644 --- a/rust-engine/src/backends/rust.rs +++ b/rust-engine/src/backends/rust.rs @@ -30,7 +30,7 @@ const _: () = { impl<'a, 'b, A: 'a + Clone> PrettyAst<'a, 'b, A> for Allocator { fn module(&'a self, module: &'b Module) -> pretty::DocBuilder<'a, Self, A> { - intersperse!(iter_pretty!(module.items), docs![hardline!(), hardline!()]) + intersperse!(&module.items, docs![hardline!(), hardline!()]) } fn item(&'a self, item: &'b Item) -> pretty::DocBuilder<'a, Self, A> { docs![&item.meta, item.kind()] @@ -49,7 +49,7 @@ const _: () = { text!("fn"), space!(), name, - intersperse!(iter_pretty!(params), docs![",", line!()]) + intersperse!(params, docs![",", line!()]) .enclose(line_!(), line_!()) .nest(INDENT) .parens() diff --git a/rust-engine/src/printer/pretty_ast.rs b/rust-engine/src/printer/pretty_ast.rs index 0975f339f..0c9607aca 100644 --- a/rust-engine/src/printer/pretty_ast.rs +++ b/rust-engine/src/printer/pretty_ast.rs @@ -132,16 +132,9 @@ pub use todo_document; /// [`pretty::DocAllocator::column`], [`pretty::DocAllocator::nesting`], /// [`pretty::DocAllocator::reflow`]. /// - a partially applied version of [`pretty::docs!`]. -/// - [`iter_pretty!`]: expands to `iter.map(|x| x.pretty(alloc_ident))`. /// - [`todo_document!`]: produce a placeholder document (that does not panic). macro_rules! install_pretty_helpers { ($allocator:ident : $allocator_type:ty) => { - /// `iter_pretty(e)` is a shorthand for `e.iter().map(|e| docs![e])` - #[allow(unused)] - macro_rules! iter_pretty { - ($e:expr) => {$e.iter().map(|el| el.pretty($allocator))}; - } - $crate::printer::pretty_ast::install_pretty_helpers!( @$allocator, #[doc = ::std::concat!("Proxy macro for [`", stringify!($crate), "::printer::pretty_ast::todo_document`] that automatically uses `", stringify!($allocator),"` as allocator.")] From 8fdd847151d86b145283eca326e94589a5464bac Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 14 Aug 2025 10:54:51 +0200 Subject: [PATCH 18/18] wip --- rust-engine/Bacon.toml | 2 + rust-engine/src/ast/resugared.rs | 4 +- rust-engine/src/printer.rs | 1 + rust-engine/src/printer/example.rs | 631 +++++++++++++++++++++++++++++ rust-engine/src/printer/parens.rs | 299 ++++++++++++++ 5 files changed, 936 insertions(+), 1 deletion(-) create mode 100644 rust-engine/Bacon.toml create mode 100644 rust-engine/src/printer/example.rs create mode 100644 rust-engine/src/printer/parens.rs diff --git a/rust-engine/Bacon.toml b/rust-engine/Bacon.toml new file mode 100644 index 000000000..094e517a5 --- /dev/null +++ b/rust-engine/Bacon.toml @@ -0,0 +1,2 @@ +[jobs.clippy] +command = ["cargo", "clippy", "--no-deps"] diff --git a/rust-engine/src/ast/resugared.rs b/rust-engine/src/ast/resugared.rs index 673b74bb5..1d4b18b2f 100644 --- a/rust-engine/src/ast/resugared.rs +++ b/rust-engine/src/ast/resugared.rs @@ -19,7 +19,9 @@ pub enum ResugaredItemKind {} /// Resugared variants for expressions. This represent extra printing-only expressions, see [`super::ExprKind::Resugared`]. #[derive_group_for_ast] -pub enum ResugaredExprKind {} +pub enum ResugaredExprKind { + Delimiter(super::Expr), +} /// Resugared variants for patterns. This represent extra printing-only patterns, see [`super::PatKind::Resugared`]. #[derive_group_for_ast] diff --git a/rust-engine/src/printer.rs b/rust-engine/src/printer.rs index 872e87b82..12ef9a368 100644 --- a/rust-engine/src/printer.rs +++ b/rust-engine/src/printer.rs @@ -10,6 +10,7 @@ //! emitted code idiomatic for the target language before pretty-printing. mod allocator; +mod parens; use std::ops::Deref; pub use allocator::Allocator; diff --git a/rust-engine/src/printer/example.rs b/rust-engine/src/printer/example.rs new file mode 100644 index 000000000..d11356836 --- /dev/null +++ b/rust-engine/src/printer/example.rs @@ -0,0 +1,631 @@ +use pretty::DocAllocator; + +/// Rust backend +use super::prelude::*; + +pub struct Rust; + +impl Printer for Rust { + fn resugaring_phases() -> Vec> { + vec![] + } + + const NAME: &str = "Rust"; +} + +const INDENT: isize = 4; + +#[prepend_associated_functions_with(install_pretty_helpers!(self: Self))] +// Note: the `const` wrapping makes my IDE and LSP happy. Otherwise, I don't get +// autocompletion of methods in the impl block below. +const _: () = { + macro_rules! todo { + ($($tt:tt)*) => { + todo_doc!($($tt)*) + }; + } + macro_rules! line { + ($($tt:tt)*) => { + dline!($($tt)*) + }; + } + macro_rules! concat { + ($($tt:tt)*) => { + dconcat!($($tt)*) + }; + } + + macro_rules! sep { + ($l:expr, ..$iterable:expr, $r:expr, $sep:expr$(,)?) => { + sep!($l, iter_pretty!($iterable), $r, $sep) + }; + ($l:expr, $it:expr, $r:expr, $sep:expr$(,)?) => { + docs![ + intersperse!($it, docs![$sep, line!()]), + docs![","].flat_alt(nil!()) + ] + .enclose(line_!(), line_!()) + .nest(INDENT) + .enclose($l, $r) + .group() + }; + ($l:expr, ..$iterable:expr, $r:expr$(,)?) => { + sep!($l, ..$iterable, $r, ",") + }; + ($l:expr, $it:expr, $r:expr$(,)?) => { + sep!($l, $it, $r, ",") + }; + } + + macro_rules! sep_opt { + (@$l:expr, $it:expr, $($rest:tt)*) => { + { + let mut it = $it.peekable(); + if it.peek().is_some() { + sep!($l, it, $($rest)*) + } else { + nil!() + } + } + }; + ($l:expr, ..$iterable:expr, $($rest:tt)*) => { + sep_opt!(@$l, iter_pretty!($iterable), $($rest)*) + }; + ($l:expr, $it:expr, $($rest:tt)*) => { + sep_opt!(@$l, $it, $($rest)*) + }; + } + + #[extension_traits::extension(trait LocalHelper)] + impl<'a, 'b, A: 'a + Clone> Allocator + where + Self: DocAllocator<'a, A> + Printer, + >::Doc: Clone, + { + fn generic_params( + &'a self, + generic_params: &'b [GenericParam], + ) -> pretty::DocBuilder<'a, Self, A> { + sep_opt!("<", ..generic_params, ">") + } + fn where_clause( + &'a self, + constraints: &'b [GenericConstraint], + ) -> pretty::DocBuilder<'a, Self, A> { + if constraints.is_empty() { + return nil!(); + } + docs![ + line!(), + "where", + line!(), + intersperse!(iter_pretty!(constraints), docs![",", line!()]) + .nest(INDENT) + .group(), + line!(), + ] + .nest(INDENT) + .group() + } + fn attributes(&'a self, attrs: &'b [Attribute]) -> pretty::DocBuilder<'a, Self, A> { + concat!( + attrs + .iter() + .filter(|attr| match &attr.kind { + AttributeKind::Tool { .. } => false, + AttributeKind::DocComment { .. } => true, + }) + .map(|attr| docs![attr, hardline!()]) + ) + } + } + + impl<'a, 'b, A: 'a + Clone> PrettyAst<'a, 'b, A> for Allocator { + fn module(&'a self, module: &'b Module) -> pretty::DocBuilder<'a, Self, A> { + intersperse!(iter_pretty!(module.items), docs![hardline!(), hardline!()]) + } + fn safety_kind(&'a self, safety_kind: &'b SafetyKind) -> pretty::DocBuilder<'a, Self, A> { + match safety_kind { + SafetyKind::Safe => nil!(), + SafetyKind::Unsafe => docs![text!("unsafe"), space!()], + } + } + fn param(&'a self, param: &'b Param) -> pretty::DocBuilder<'a, Self, A> { + docs![¶m.pat, ":", space!(), ¶m.ty] + } + fn binding_mode( + &'a self, + binding_mode: &'b BindingMode, + ) -> pretty::DocBuilder<'a, Self, A> { + match binding_mode { + BindingMode::ByRef(BorrowKind::Mut) => docs!["mut", space!()], + _ => nil!(), + } + } + fn pat(&'a self, pat: &'b Pat) -> pretty::DocBuilder<'a, Self, A> { + match &*pat.kind { + PatKind::Wild => docs!["_"], + PatKind::Ascription { pat, ty } => docs![pat, ":", space!(), ty], + PatKind::Or { sub_pats } => { + intersperse!(iter_pretty!(sub_pats), docs![line!(), "|", line!()]) + } + PatKind::Array { args } => sep!("[", ..args, "]", "|"), + PatKind::Deref { sub_pat } => todo!(), + PatKind::Constant { lit } => todo!(), + PatKind::Binding { + mutable, + var, + mode, + sub_pat, + } => { + docs![ + if *mutable { + docs!["mut", space!()] + } else { + nil!() + }, + mode, + var, + sub_pat.as_ref().map(|pat| docs!["@", p!(pat)]), + ] + } + PatKind::Construct { + constructor, + is_record, + is_struct, + fields, + } => todo!(), + PatKind::Resugared(resugared_pat_kind) => todo!(), + PatKind::Error(error_node) => todo!(), + } + } + fn primitive_ty( + &'a self, + primitive_ty: &'b PrimitiveTy, + ) -> pretty::DocBuilder<'a, Self, A> { + match primitive_ty { + PrimitiveTy::Bool => todo!(), + PrimitiveTy::Int(int_kind) => docs![int_kind], + PrimitiveTy::Float(float_kind) => todo!(), + PrimitiveTy::Char => todo!(), + PrimitiveTy::Str => todo!(), + } + } + fn signedness( + &'a self, + signedness: &'b literals::Signedness, + ) -> pretty::DocBuilder<'a, Self, A> { + match signedness { + literals::Signedness::Signed => docs!["i"], + literals::Signedness::Unsigned => docs!["u"], + } + } + fn int_size(&'a self, int_size: &'b literals::IntSize) -> pretty::DocBuilder<'a, Self, A> { + docs![match int_size { + literals::IntSize::S8 => "8", + literals::IntSize::S16 => "16", + literals::IntSize::S32 => "32", + literals::IntSize::S64 => "64", + literals::IntSize::S128 => "128", + literals::IntSize::SSize => "size", + }] + } + fn generic_param( + &'a self, + generic_param: &'b GenericParam, + ) -> pretty::DocBuilder<'a, Self, A> { + docs![ + match &generic_param.kind { + GenericParamKind::Const { .. } => docs!["const", space!()], + _ => nil!(), + }, + &generic_param.ident, + match &generic_param.kind { + GenericParamKind::Const { ty } => docs![":", space!(), ty], + _ => nil!(), + } + ] + } + fn generic_constraint( + &'a self, + generic_constraint: &'b GenericConstraint, + ) -> pretty::DocBuilder<'a, Self, A> { + match generic_constraint { + GenericConstraint::Lifetime(s) => docs![s.clone()], + GenericConstraint::Type(impl_ident) => docs![impl_ident], + GenericConstraint::Projection(projection_predicate) => docs![projection_predicate], + } + } + fn impl_ident(&'a self, impl_ident: &'b ImplIdent) -> pretty::DocBuilder<'a, Self, A> { + let trait_goal = &impl_ident.goal; + let [self_ty, args @ ..] = &trait_goal.args[..] else { + panic!() + }; + docs![ + self_ty, + space!(), + ":", + space!(), + &trait_goal.trait_, + sep_opt!("<", ..args, ">"), + ] + } + fn ty(&'a self, ty: &'b Ty) -> pretty::DocBuilder<'a, Self, A> { + match ty.kind() { + TyKind::Primitive(primitive_ty) => docs![primitive_ty], + TyKind::Tuple(items) => intersperse!(iter_pretty!(items), docs![",", line!()]) + .nest(INDENT) + .group(), + TyKind::App { head, args } => docs![head, sep_opt!("<", ..args, ">")], + TyKind::Arrow { inputs, output } => todo!(), + TyKind::Ref { + inner, + mutable, + region, + } => docs![ + "&", + if *mutable { + docs!["mut", space!()] + } else { + nil!() + }, + inner + ], + TyKind::Param(local_id) => docs![local_id], + TyKind::Slice(ty) => docs![ty].enclose("[", "]"), + TyKind::Array { ty, length } => todo!(), + TyKind::RawPointer => todo!(), + TyKind::AssociatedType { impl_, item } => todo!(), + TyKind::Opaque(global_id) => docs![global_id], + TyKind::Dyn(dyn_trait_goals) => docs![ + "dyn", + docs![ + line!(), + intersperse!(dyn_trait_goals, docs![line!(), "+", space!()]) + ] + .group() + .hang(0) + ], + TyKind::Resugared(resugared_ty_kind) => todo!(), + TyKind::Error(error_node) => todo!(), + } + } + fn literal(&'a self, literal: &'b literals::Literal) -> pretty::DocBuilder<'a, Self, A> { + match literal { + literals::Literal::String(symbol) => docs![symbol], + literals::Literal::Char(ch) => text!(format!("{}", ch)), + literals::Literal::Bool(b) => text!(format!("{}", b)), + literals::Literal::Int { + value, + negative, + kind, + } => docs![if *negative { p!("-") } else { nil!() }, value, kind], + literals::Literal::Float { + value, + negative, + kind, + } => todo!(), + } + } + fn int_kind(&'a self, int_kind: &'b literals::IntKind) -> pretty::DocBuilder<'a, Self, A> { + docs![&int_kind.signedness, &int_kind.size] + } + fn trait_goal(&'a self, trait_goal: &'b TraitGoal) -> pretty::DocBuilder<'a, Self, A> { + let [self_ty, args @ ..] = &trait_goal.args[..] else { + panic!() + }; + docs![ + self_ty, + space!(), + "as", + space!(), + &trait_goal.trait_, + sep_opt!("<", ..args, ">"), + ] + .enclose("<", ">") + } + fn generic_value( + &'a self, + generic_value: &'b GenericValue, + ) -> pretty::DocBuilder<'a, Self, A> { + match generic_value { + GenericValue::Ty(ty) => docs![ty], + GenericValue::Expr(expr) => docs![expr], + GenericValue::Lifetime => docs!["'_"], + } + } + fn expr(&'a self, expr: &'b Expr) -> pretty::DocBuilder<'a, Self, A> { + match &*expr.kind { + ExprKind::If { + condition, + then, + else_, + } => docs![ + "if", + space!(), + p!(condition).parens(), + p!(then).braces(), + "else", + opt!(else_).map(|doc| doc.braces()).unwrap_or(nil!()) + ], + ExprKind::App { + head, + args, + generic_args, + bounds_impls: _, + trait_, + } => docs![ + head, + trait_ + .iter() + .map(|(ii, _generic_values)| docs![&ii.goal]) + .next() + .unwrap_or(nil!()), + sep_opt!("<", ..generic_args, ">"), + sep!("(", ..args, ")") + ], + ExprKind::Literal(literal) => docs![literal], + ExprKind::Array(exprs) => sep!("[", ..exprs, "]"), + ExprKind::Construct { + constructor, + is_record, + is_struct, + fields, + base, + } => { + let payload = fields.iter().map(|(id, value)| { + docs![ + if *is_record { + docs![id, ":", space!()] + } else { + nil!() + }, + value + ] + }); + docs![ + constructor, + if *is_record { + sep!("{", payload, "}") + } else { + sep!("(", payload, ")") + } + ] + } + ExprKind::Match { scrutinee, arms } => { + docs!["match", scrutinee, sep!("{", ..arms, "}")] + } + ExprKind::Tuple(exprs) => todo!(), + ExprKind::Borrow { mutable, inner } => todo!(), + ExprKind::AddressOf { mutable, inner } => todo!(), + ExprKind::Deref(expr) => todo!(), + ExprKind::Let { lhs, rhs, body } => docs![ + "let", + space!(), + lhs, + space!(), + "=", + docs![line!(), rhs].group().nest(INDENT), + ";", + hardline!(), + body + ], + ExprKind::GlobalId(global_id) => docs![global_id], + ExprKind::LocalId(local_id) => docs![local_id], + ExprKind::Ascription { e, ty } => docs![e, ":", space!(), ty].parens(), + ExprKind::Assign { lhs, value } => docs![lhs, space!(), "=", space!(), value], + ExprKind::Loop { + body, + kind, + state, + control_flow, + label, + } => todo!(), + ExprKind::Break { value, label } => docs!["break", space!(), value], + ExprKind::Return { value } => docs!["return", space!(), value], + ExprKind::Continue { label } => docs!["continue"], + ExprKind::Closure { + params, + body, + captures: _, + } => docs![ + intersperse!(iter_pretty!(params), docs![",", space!()]).enclose("|", "|"), + body + ], + ExprKind::Block { body, safety_mode } => { + docs![ + safety_mode, + docs![line_!(), body, line_!()] + .group() + .braces() + .nest(INDENT) + ] + } + ExprKind::Quote { contents } => todo!(), + ExprKind::Resugared(resugared_expr_kind) => todo!(), + ExprKind::Error(error_node) => todo!(), + } + } + fn lhs(&'a self, lhs: &'b Lhs) -> pretty::DocBuilder<'a, Self, A> { + match lhs { + Lhs::LocalVar { var, ty: _ } => docs![var], + Lhs::ArbitraryExpr(expr) => docs![std::ops::Deref::deref(expr)], + Lhs::FieldAccessor { e, ty: _, field } => { + docs![std::ops::Deref::deref(e), ".", field] + } + Lhs::ArrayAccessor { e, ty: _, index } => { + docs![std::ops::Deref::deref(e), p!(index).enclose("[", "]")] + } + } + } + fn global_id( + &'a self, + global_id: &'b identifiers::GlobalId, + ) -> pretty::DocBuilder<'a, Self, A> { + docs![global_id.to_debug_string()] + } + fn variant(&'a self, variant: &'b Variant) -> pretty::DocBuilder<'a, Self, A> { + let payload = variant.arguments.iter().map(|(id, ty, attrs)| { + docs![ + self.attributes(attrs), + if variant.is_record { + docs![id, ":", space!()] + } else { + nil!() + }, + ty + ] + }); + + if variant.is_record { + sep!("{", payload, "}") + } else { + sep!("(", payload, ")") + } + } + fn item(&'a self, item: &'b Item) -> pretty::DocBuilder<'a, Self, A> { + docs![&item.meta, item.kind()] + } + fn item_kind(&'a self, item_kind: &'b ItemKind) -> pretty::DocBuilder<'a, Self, A> { + match item_kind { + ItemKind::Fn { + name, + generics, + body, + params, + safety, + } => { + docs![ + safety, + text!("fn"), + space!(), + name, + self.generic_params(&generics.params), + sep!("(", ..params, ")"), + self.where_clause(&generics.constraints), + docs![line_!(), body, line_!(),].nest(INDENT).braces() + ] + } + ItemKind::TyAlias { + name, + generics: _, + ty, + } => docs!["type", space!(), name, space!(), "=", space!(), ty, ";"], + ItemKind::Type { + name, + generics, + variants, + is_struct, + } => match &variants[..] { + [variant] if *is_struct => { + docs![ + "struct", + space!(), + name, + self.generic_params(&generics.params), + variant, + if variant.is_record { + nil!() + } else { + docs![";"] + } + ] + } + _ => { + docs![ + "enum", + space!(), + name, + self.generic_params(&generics.params), + sep!( + "{", + variants.iter().map(|variant| docs![ + &variant.name, + space!(), + variant + ]), + "}", + ), + self.where_clause(&generics.constraints), + ] + } + }, + ItemKind::Trait { + name, + generics, + items, + } => docs![ + "trait", + space!(), + name, + self.generic_params(&generics.params), + self.where_clause(&generics.constraints), + sep!("{", items, "}", nil!()), + ], + ItemKind::Impl { + generics, + self_ty, + of_trait: (trait_, trait_args), + items, + parent_bounds: _, + safety, + } => docs![ + safety, + "impl", + self.generic_params(&generics.params), + space!(), + trait_, + sep_opt!("<", ..trait_args, ">"), + space!(), + "for", + self_ty, + self.where_clause(&generics.constraints), + sep!("{", items, "}", nil!()), + ], + ItemKind::Alias { name, item } => todo!(), + ItemKind::Use { + path, + is_external, + rename, + } => todo!(), + ItemKind::Quote { quote, origin } => todo!(), + ItemKind::Error(error_node) => todo!(), + ItemKind::Resugared(resugared_item_kind) => todo!(), + ItemKind::NotImplementedYet => todo!(), + } + } + fn impl_item(&'a self, impl_item: &'b ImplItem) -> pretty::DocBuilder<'a, Self, A> { + match &impl_item.kind { + ImplItemKind::Type { ty, parent_bounds } => todo!(), + ImplItemKind::Fn { body, params } => docs![ + &impl_item.meta, + text!("fn"), + space!(), + &impl_item.ident, + self.generic_params(&impl_item.generics.params), + sep!("(", ..params, ")"), + self.where_clause(&impl_item.generics.constraints), + docs![line_!(), body, line_!(),].nest(INDENT).braces() + ], + ImplItemKind::Resugared(resugared_impl_item_kind) => todo!(), + } + } + fn metadata(&'a self, metadata: &'b Metadata) -> pretty::DocBuilder<'a, Self, A> { + self.attributes(&metadata.attributes) + } + fn attribute(&'a self, attribute: &'b Attribute) -> pretty::DocBuilder<'a, Self, A> { + match &attribute.kind { + AttributeKind::Tool { path, tokens } => { + // docs!["#", "[", path.clone(), "(", tokens.clone(), ")", "]"] + nil!() + } + AttributeKind::DocComment { kind, body } => match kind { + DocCommentKind::Line => { + concat!(body.lines().map(|line| docs![format!("/// {line}")])) + } + DocCommentKind::Block => todo!(), + }, + } + } + } +}; diff --git a/rust-engine/src/printer/parens.rs b/rust-engine/src/printer/parens.rs new file mode 100644 index 000000000..9ff09e18a --- /dev/null +++ b/rust-engine/src/printer/parens.rs @@ -0,0 +1,299 @@ +use std::mem; + +use super::*; +use crate::ast::{identifiers::*, literals::*, resugared::*, visitors::AstVisitorMut, *}; +use crate::symbol::*; + +pub struct Parens { + precedence_level: PrecedenceLevel, + printer: P, +} + +impl Parens

{ + fn with_precedence( + &mut self, + precedence_level: PrecedenceLevel, + mut action: impl FnMut(&mut Self) -> T, + ) -> T { + let previous = self.precedence_level; + self.precedence_level = precedence_level; + let result = action(self); + self.precedence_level = previous; + result + } +} + +impl AstVisitorMut for Parens

{ + fn exit_expr(&mut self, e: &mut Expr) { + if self.precedence_for_expr_kind(&e.kind) > self.precedence_level { + *e.kind = ExprKind::Resugared(ResugaredExprKind::Delimiter(e.clone())); + }; + } + fn visit_expr_kind(&mut self, x: &mut ExprKind) { + self.visit_expr_kind_with_precedence(x); + } +} + +macro_rules! mk { + (enum $name:ident { + $( + $variant:ident + $({ + $($rec_ident:ident : $rec_ty:ty),* + $(,)? + })? + $(( + $($non_rec_ty:ty),* + $(,)? + ))? + ),* + $(,)? + }) => { + pub trait Precedences { + $( + mk!(@prec_method $name $variant; $($($rec_ident : $rec_ty),*)?); + )* + } + mk!(@pre_macro_rule {$({$name $variant; $($($rec_ident : $rec_ty),*)?})*} {} {$($name $variant),*}); + + impl Parens

{ + pastey::paste! { + fn [< precedence_for_ $name:snake >](&self, x: &$name) -> PrecedenceLevel { + match x { + $( + $name::$variant {..} => self.printer.[< prec_ $name:snake _ $variant:snake >](x), + )* + _ => todo!(), + } + } + fn [< visit_ $name:snake _with_precedence >](&mut self, x: &mut $name) { + match x { + $( + $name::$variant {$($($rec_ident),*)?} => { + mk!(@parens_arm $name $variant self x; $($($rec_ident : $rec_ty),*)?); + }, + )* + _ => todo!(), + } + } + } + } + }; + + (@parens_arm $name:ident $variant:ident $self:ident $x:ident; $($field_name:tt : $field_ty:ty),*) => { + pastey::paste! { + $( + $self.with_precedence( + $self.printer.[< prec_ $name:snake _ $variant:snake _ $field_name >]($field_name), + |this| this.visit($field_name), + ); + )* + } + }; + + (@prec_method $name:ident $variant:ident; $($field_name:tt : $field_ty:ty),*) => { + pastey::paste! { + fn [< prec_ $name:snake _ $variant:snake >](&self, [< $name:snake >]: &$name) -> PrecedenceLevel { + let _ = [< $name:snake >]; + PrecedenceLevel::Inherit + } + $( + fn [< prec_ $name:snake _ $variant:snake _ $field_name >](&self, $field_name: &$field_ty) -> PrecedenceLevel { + let _ = $field_name; + PrecedenceLevel::Inherit + } + )* + } + }; + + (@pre_macro_rule + {} + {$($acc:tt)*} + {$($just_variants:tt)*} + ) => { + mk!(@macro_rule {$($just_variants)*} $($acc)*); + }; + (@pre_macro_rule + { + {$name:ident $variant:ident; $($field_name:tt : $field_ty:ty),*} + $($rest:tt)* + } + {$($acc:tt)*} + {$($just_variants:tt)*} + ) => { + mk!(@pre_macro_rule {$($rest)*} {$($name $variant $field_name $field_ty;)* $($acc)*} {$($just_variants)*}); + }; + (@macro_rule {$($vname:ident $vvariant:ident),*} $($name:ident $variant:ident $field_name:tt $field_ty:ty;)*) => { + pastey::paste! { + macro_rules! hello { + $( + (@set $vname $vvariant = |$binder:pat_param| $prec_body:expr) => { + fn [< prec_ $vname:snake _ $vvariant:snake >](&self, $binder: &$vname) -> PrecedenceLevel { + $prec_body + } + }; + )* + $( + (@set $name $variant $field_name = |$binder:pat_param| $prec_body:expr) => { + fn [< prec_ $name:snake _ $variant:snake _ $field_name >](&self, #[allow(unused)]$binder: &$field_ty) -> PrecedenceLevel { + $prec_body + } + }; + )* + } + } + }; +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PrecedenceLevel { + Inherit, + MinusInfinity, + Value(isize), +} + +impl PartialOrd for PrecedenceLevel { + fn partial_cmp(&self, other: &Self) -> Option { + todo!() + } +} + +macro_rules! set { + {} => {}; + {{$($path:ident)*}$(,)?} => {}; + {{$($path:ident)*} {$($value:tt)*}} => { + set!({$($path)*} $($value)*); + }; + {{$($path:ident)*} $value:literal} => { + hello!(@set $($path)* = |_| PrecedenceLevel::Value($value)); + }; + {{$($path:ident)*} Inherit} => { + hello!(@set $($path)* = |_| PrecedenceLevel::Inherit); + }; + {{$($path:ident)*} Never} => { + hello!(@set $($path)* = |_| PrecedenceLevel::MinusInfinity); + }; + {{$($path:ident)*} |$binder:pat_param| $value:expr} => { + hello!(@set $($path)* = |$binder| $value); + }; + {{$($path:ident)*} $value:expr} => { + hello!(@set $($path)* = |_| $value); + }; + {{$($path:ident)*} $($key:ident).+ : {$($value:tt)*}$(, $($rest:tt)*)?} => { + set!({$($path)* $($key)*} $($value)*); + set!({$($path)*} $($($rest)*)?); + }; + {{$($path:ident)*} self : $value:expr$(, $($rest:tt)*)?} => { + set!({$($path)*} $value); + set!({$($path)*} $($($rest)*)?); + }; + {{$($path:ident)*} $($key:ident).+ : $value:expr$(, $($rest:tt)*)?} => { + set!({$($path)* $($key)*} $value); + set!({$($path)*} $($($rest)*)?); + }; + {{$($value:tt)+}} => { + set!({} $($value)*); + }; + {$($key:ident).+ : $($rest:tt)*} => { + set!({} $($key).+ : $($rest)*); + }; +} + +mk!( + enum ExprKind { + If { + condition: Expr, + then: Expr, + else_: Option, + }, + App { + head: Expr, + args: Vec, + generic_args: Vec, + bounds_impls: Vec, + trait_: Option<(ImplExpr, Vec)>, + }, + // Literal(Literal), + // Array(Vec), + Construct { + constructor: GlobalId, + is_record: bool, + is_struct: bool, + fields: Vec<(GlobalId, Expr)>, + base: Option, + }, + Match { + scrutinee: Expr, + arms: Vec, + }, + // Tuple(Vec), + Borrow { + mutable: bool, + inner: Expr, + }, + AddressOf { + mutable: bool, + inner: Expr, + }, + // Deref(Expr), + Let { + lhs: Pat, + rhs: Expr, + body: Expr, + }, + // GlobalId(GlobalId), + // LocalId(LocalId), + Ascription { + e: Expr, + ty: Ty, + }, + Assign { + lhs: Lhs, + value: Expr, + }, + Loop { + body: Expr, + kind: LoopKind, + state: Option, + control_flow: Option, + label: Option, + }, + Break { + value: Expr, + label: Option, + }, + Return { + value: Expr, + }, + Continue { + label: Option, + }, + Closure { + params: Vec, + body: Expr, + captures: Vec, + }, + Block { + body: Expr, + safety_mode: SafetyKind, + }, + Quote { + contents: Quote, + }, + // Resugared(ResugaredExprKind), + // Error(ErrorNode), + } +); + +struct Rust; + +impl Precedences for Rust { + set!( + ExprKind: { + Closure: { + self: 10, + params: 50 + }, + }, + ); +}