Skip to main content

m2_syn/
expansion.rs

1//! Interfaces for expanding Macaulay2 syntax with Rust code.
2
3use std::error::Error;
4use std::fmt::{Display, Formatter};
5
6use crate::CellStream;
7use crate::fold::{self, Fold};
8use crate::nodes::{Expr, ExprAssignInstallMethod, ExprLambda, ExprLiteral, TypeAnnotation};
9use crate::{CellStreamError, NativeParseError, NativeParser, Parser, Tokenize, parse_quote_m2};
10
11/// A procedural macro over independently parseable Macaulay2 cells.
12///
13/// Implementations may parse the cells into the typed syntax graph, transform
14/// raw tokens directly, perform arbitrary Rust computations, or combine those
15/// approaches. The caller decides whether expansion happens for one submitted
16/// cell or for a complete source file.
17pub trait ProcMacro<Args = ()> {
18    /// The error reported when expansion cannot produce a cell stream.
19    type Error: Error;
20
21    /// Expands `input` using the invocation arguments in `args`.
22    fn expand(&mut self, input: CellStream, args: &Args) -> Result<CellStream, Self::Error>;
23}
24
25#[derive(Debug, Default)]
26/// A procedural macro that annotates methods returning a direct literal.
27///
28/// Existing return annotations are retained. Integer, floating-point, and
29/// string literals produce `ZZ`, `RR`, and `String` annotations respectively;
30/// every other function body is left unchanged.
31///
32/// # Examples
33///
34/// ```
35/// use m2_syn::{CellStream, ProcMacro, lex_str};
36/// use m2_syn::LiteralReturnTypes;
37///
38/// let input = CellStream::new(lex_str("f = method(); f ZZ := x -> 1").unwrap()).unwrap();
39/// let output = LiteralReturnTypes.expand(input, &()).unwrap();
40/// assert!(output.to_string().contains("f ZZ :=ZZ=>x -> 1"));
41/// ```
42pub struct LiteralReturnTypes;
43
44#[derive(Debug)]
45/// A failure to parse or reconstruct input expanded by [`LiteralReturnTypes`].
46pub enum LiteralReturnTypesError {
47    /// The cells were not valid typed Macaulay2 syntax.
48    Parse(NativeParseError),
49    /// The transformed syntax could not be partitioned back into cells.
50    Tokenize(CellStreamError),
51}
52
53impl Display for LiteralReturnTypesError {
54    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
55        match self {
56            Self::Parse(error) => Display::fmt(error, formatter),
57            Self::Tokenize(error) => Display::fmt(error, formatter),
58        }
59    }
60}
61
62impl Error for LiteralReturnTypesError {
63    fn source(&self) -> Option<&(dyn Error + 'static)> {
64        match self {
65            Self::Parse(error) => Some(error),
66            Self::Tokenize(error) => Some(error),
67        }
68    }
69}
70
71impl ProcMacro for LiteralReturnTypes {
72    type Error = LiteralReturnTypesError;
73
74    fn expand(&mut self, input: CellStream, _args: &()) -> Result<CellStream, Self::Error> {
75        let mut parser = NativeParser;
76        let syntax = parser
77            .parse_cells(input)
78            .map_err(LiteralReturnTypesError::Parse)?;
79        self.fold_source_file(syntax)
80            .to_cell_stream()
81            .map_err(LiteralReturnTypesError::Tokenize)
82    }
83}
84
85impl Fold for LiteralReturnTypes {
86    fn fold_expr_assign_install_method(
87        &mut self,
88        node: ExprAssignInstallMethod,
89    ) -> ExprAssignInstallMethod {
90        let mut node = fold::fold_expr_assign_install_method(self, node);
91        if node.typical_value.is_none() {
92            node.typical_value = literal_return_annotation(&node.function);
93        }
94        node
95    }
96}
97
98/// Handles literal return annotation.
99fn literal_return_annotation(function: &Expr) -> Option<TypeAnnotation> {
100    let Expr::Lambda(lambda) = function else {
101        return None;
102    };
103    let body = match lambda {
104        ExprLambda::Variadic(lambda) => lambda.body.as_ref(),
105        ExprLambda::List(lambda) => lambda.body.as_ref(),
106    };
107    let Expr::Literal(literal) = body else {
108        return None;
109    };
110    let typical_value = match literal {
111        ExprLiteral::Integer(_) => parse_quote_m2!(ZZ),
112        ExprLiteral::Float(_) => parse_quote_m2!(RR),
113        ExprLiteral::String(_) | ExprLiteral::RawString(_) => parse_quote_m2!(String),
114    };
115    Some(TypeAnnotation::new(typical_value, parse_quote_m2!(=>)))
116}