about summary refs log tree commit diff
path: root/library/compiler-builtins/crates/libm-macros/src/parse.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/compiler-builtins/crates/libm-macros/src/parse.rs')
-rw-r--r--library/compiler-builtins/crates/libm-macros/src/parse.rs296
1 files changed, 296 insertions, 0 deletions
diff --git a/library/compiler-builtins/crates/libm-macros/src/parse.rs b/library/compiler-builtins/crates/libm-macros/src/parse.rs
new file mode 100644
index 00000000000..4876f3ef726
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/src/parse.rs
@@ -0,0 +1,296 @@
+use std::collections::BTreeMap;
+
+use proc_macro2::Span;
+use quote::ToTokens;
+use syn::parse::{Parse, ParseStream, Parser};
+use syn::punctuated::Punctuated;
+use syn::spanned::Spanned;
+use syn::token::{self, Comma};
+use syn::{Arm, Attribute, Expr, ExprMatch, Ident, LitBool, Meta, Token, bracketed};
+
+/// The input to our macro; just a list of `field: value` items.
+#[derive(Debug)]
+pub struct Invocation {
+    fields: Punctuated<Mapping, Comma>,
+}
+
+impl Parse for Invocation {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        Ok(Self {
+            fields: input.parse_terminated(Mapping::parse, Token![,])?,
+        })
+    }
+}
+
+/// A `key: expression` mapping with nothing else. Basically a simplified `syn::Field`.
+#[derive(Debug)]
+struct Mapping {
+    name: Ident,
+    _sep: Token![:],
+    expr: Expr,
+}
+
+impl Parse for Mapping {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        Ok(Self {
+            name: input.parse()?,
+            _sep: input.parse()?,
+            expr: input.parse()?,
+        })
+    }
+}
+
+/// The input provided to our proc macro, after parsing into the form we expect.
+#[derive(Debug)]
+pub struct StructuredInput {
+    /// Macro to invoke once per function
+    pub callback: Ident,
+    /// Whether or not to provide `CFn` `CArgs` `RustFn` etc. This is really only needed
+    /// once for crate to set up the main trait.
+    pub emit_types: Vec<Ident>,
+    /// Skip these functions
+    pub skip: Vec<Ident>,
+    /// If true, omit f16 and f128 functions that aren't present in other libraries.
+    pub skip_f16_f128: bool,
+    /// Invoke only for these functions
+    pub only: Option<Vec<Ident>>,
+    /// Attributes that get applied to specific functions
+    pub attributes: Option<Vec<AttributeMap>>,
+    /// Extra expressions to pass to all invocations of the macro
+    pub extra: Option<Expr>,
+    /// Per-function extra expressions to pass to the macro
+    pub fn_extra: Option<BTreeMap<Ident, Expr>>,
+    // For diagnostics
+    pub emit_types_span: Option<Span>,
+    pub only_span: Option<Span>,
+    pub fn_extra_span: Option<Span>,
+}
+
+impl StructuredInput {
+    pub fn from_fields(input: Invocation) -> syn::Result<Self> {
+        let mut map: Vec<_> = input.fields.into_iter().collect();
+        let cb_expr = expect_field(&mut map, "callback")?;
+        let emit_types_expr = expect_field(&mut map, "emit_types").ok();
+        let skip_expr = expect_field(&mut map, "skip").ok();
+        let skip_f16_f128 = expect_field(&mut map, "skip_f16_f128").ok();
+        let only_expr = expect_field(&mut map, "only").ok();
+        let attr_expr = expect_field(&mut map, "attributes").ok();
+        let extra = expect_field(&mut map, "extra").ok();
+        let fn_extra = expect_field(&mut map, "fn_extra").ok();
+
+        if !map.is_empty() {
+            Err(syn::Error::new(
+                map.first().unwrap().name.span(),
+                format!("unexpected fields {map:?}"),
+            ))?;
+        }
+
+        let emit_types_span = emit_types_expr.as_ref().map(|expr| expr.span());
+        let emit_types = match emit_types_expr {
+            Some(expr) => Parser::parse2(parse_ident_or_array, expr.into_token_stream())?,
+            None => Vec::new(),
+        };
+
+        let skip = match skip_expr {
+            Some(expr) => Parser::parse2(parse_ident_array, expr.into_token_stream())?,
+            None => Vec::new(),
+        };
+
+        let skip_f16_f128 = match skip_f16_f128 {
+            Some(expr) => expect_litbool(expr)?.value,
+            None => false,
+        };
+
+        let only_span = only_expr.as_ref().map(|expr| expr.span());
+        let only = match only_expr {
+            Some(expr) => Some(Parser::parse2(parse_ident_array, expr.into_token_stream())?),
+            None => None,
+        };
+
+        let attributes = match attr_expr {
+            Some(expr) => {
+                let mut attributes = Vec::new();
+                let attr_exprs = Parser::parse2(parse_expr_array, expr.into_token_stream())?;
+
+                for attr in attr_exprs {
+                    attributes.push(syn::parse2(attr.into_token_stream())?);
+                }
+                Some(attributes)
+            }
+            None => None,
+        };
+
+        let fn_extra_span = fn_extra.as_ref().map(|expr| expr.span());
+        let fn_extra = match fn_extra {
+            Some(expr) => Some(extract_fn_extra_field(expr)?),
+            None => None,
+        };
+
+        Ok(Self {
+            callback: expect_ident(cb_expr)?,
+            emit_types,
+            skip,
+            skip_f16_f128,
+            only,
+            only_span,
+            attributes,
+            extra,
+            fn_extra,
+            fn_extra_span,
+            emit_types_span,
+        })
+    }
+}
+
+fn extract_fn_extra_field(expr: Expr) -> syn::Result<BTreeMap<Ident, Expr>> {
+    let Expr::Match(mexpr) = expr else {
+        let e = syn::Error::new(expr.span(), "`fn_extra` expects a match expression");
+        return Err(e);
+    };
+
+    let ExprMatch {
+        attrs,
+        match_token: _,
+        expr,
+        brace_token: _,
+        arms,
+    } = mexpr;
+
+    expect_empty_attrs(&attrs)?;
+
+    let match_on = expect_ident(*expr)?;
+    if match_on != "MACRO_FN_NAME" {
+        let e = syn::Error::new(match_on.span(), "only allowed to match on `MACRO_FN_NAME`");
+        return Err(e);
+    }
+
+    let mut res = BTreeMap::new();
+
+    for arm in arms {
+        let Arm {
+            attrs,
+            pat,
+            guard,
+            fat_arrow_token: _,
+            body,
+            comma: _,
+        } = arm;
+
+        expect_empty_attrs(&attrs)?;
+
+        let keys = match pat {
+            syn::Pat::Wild(w) => vec![Ident::new("_", w.span())],
+            _ => Parser::parse2(parse_ident_pat, pat.into_token_stream())?,
+        };
+
+        if let Some(guard) = guard {
+            let e = syn::Error::new(guard.0.span(), "no guards allowed in this position");
+            return Err(e);
+        }
+
+        for key in keys {
+            let inserted = res.insert(key.clone(), *body.clone());
+            if inserted.is_some() {
+                let e = syn::Error::new(key.span(), format!("key `{key}` specified twice"));
+                return Err(e);
+            }
+        }
+    }
+
+    Ok(res)
+}
+
+fn expect_empty_attrs(attrs: &[Attribute]) -> syn::Result<()> {
+    if attrs.is_empty() {
+        return Ok(());
+    }
+
+    let e = syn::Error::new(
+        attrs.first().unwrap().span(),
+        "no attributes allowed in this position",
+    );
+    Err(e)
+}
+
+/// Extract a named field from a map, raising an error if it doesn't exist.
+fn expect_field(v: &mut Vec<Mapping>, name: &str) -> syn::Result<Expr> {
+    let pos = v.iter().position(|v| v.name == name).ok_or_else(|| {
+        syn::Error::new(
+            Span::call_site(),
+            format!("missing expected field `{name}`"),
+        )
+    })?;
+
+    Ok(v.remove(pos).expr)
+}
+
+/// Coerce an expression into a simple identifier.
+fn expect_ident(expr: Expr) -> syn::Result<Ident> {
+    syn::parse2(expr.into_token_stream())
+}
+
+/// Coerce an expression into a simple keyword.
+fn expect_litbool(expr: Expr) -> syn::Result<LitBool> {
+    syn::parse2(expr.into_token_stream())
+}
+
+/// Parse either a single identifier (`foo`) or an array of identifiers (`[foo, bar, baz]`).
+fn parse_ident_or_array(input: ParseStream) -> syn::Result<Vec<Ident>> {
+    if !input.peek(token::Bracket) {
+        return Ok(vec![input.parse()?]);
+    }
+
+    parse_ident_array(input)
+}
+
+/// Parse an array of expressions.
+fn parse_expr_array(input: ParseStream) -> syn::Result<Vec<Expr>> {
+    let content;
+    let _ = bracketed!(content in input);
+    let fields = content.parse_terminated(Expr::parse, Token![,])?;
+    Ok(fields.into_iter().collect())
+}
+
+/// Parse an array of idents, e.g. `[foo, bar, baz]`.
+fn parse_ident_array(input: ParseStream) -> syn::Result<Vec<Ident>> {
+    let content;
+    let _ = bracketed!(content in input);
+    let fields = content.parse_terminated(Ident::parse, Token![,])?;
+    Ok(fields.into_iter().collect())
+}
+
+/// Parse an pattern of idents, specifically `(foo | bar | baz)`.
+fn parse_ident_pat(input: ParseStream) -> syn::Result<Vec<Ident>> {
+    if !input.peek2(Token![|]) {
+        return Ok(vec![input.parse()?]);
+    }
+
+    let fields = Punctuated::<Ident, Token![|]>::parse_separated_nonempty(input)?;
+    Ok(fields.into_iter().collect())
+}
+
+/// A mapping of attributes to identifiers (just a simplified `Expr`).
+///
+/// Expressed as:
+///
+/// ```ignore
+/// #[meta1]
+/// #[meta2]
+/// [foo, bar, baz]
+/// ```
+#[derive(Debug)]
+pub struct AttributeMap {
+    pub meta: Vec<Meta>,
+    pub names: Vec<Ident>,
+}
+
+impl Parse for AttributeMap {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let attrs = input.call(Attribute::parse_outer)?;
+
+        Ok(Self {
+            meta: attrs.into_iter().map(|a| a.meta).collect(),
+            names: parse_ident_array(input)?,
+        })
+    }
+}