gl_headless_macros/
lib.rs

1//! See [`gl-headless` docs] instead.
2//!
3//! [`gl-headless` docs]: https://docs.rs/gl-headless/*/gl_headless/
4
5#![forbid(unsafe_code)]
6#![forbid(elided_lifetimes_in_paths)]
7
8use std::fmt;
9
10use proc_macro::TokenStream;
11use quote::{quote_spanned, ToTokens};
12use syn::spanned::Spanned;
13use syn::{parse_macro_input, FnArg, ItemFn, LitStr, Pat};
14
15/// Creates a headless OpenGL context, that is valid throughout
16/// the scope of the function.
17///
18/// See examples in the [crate root].
19///
20/// # Attributes
21///
22/// - `version = "3.3"`: Specify the OpenGL version, e.g.:
23///   `#[gl_headless(version = "3.3")]`
24///
25/// # Example
26///
27/// ```toml
28/// [dependencies]
29/// gl = "0.14"
30/// gl-headless = "0.2"
31/// ```
32///
33/// ```rust
34/// use gl_headless::gl_headless;
35///
36/// #[gl_headless]
37/// unsafe fn main() {
38///     let (mut major, mut minor) = (0, 0);
39///     gl::GetIntegerv(gl::MAJOR_VERSION, &mut major);
40///     gl::GetIntegerv(gl::MINOR_VERSION, &mut minor);
41///     println!("OpenGL {major}.{minor}");
42/// }
43/// ```
44///
45/// [crate root]: https://docs.rs/gl-headless/*/gl_headless/
46#[proc_macro_attribute]
47pub fn gl_headless(args: TokenStream, item: TokenStream) -> TokenStream {
48    let mut version: Option<LitStr> = None;
49    let args_parser = syn::meta::parser(|meta| {
50        if meta.path.is_ident("version") {
51            version = Some(meta.value()?.parse()?);
52            Ok(())
53        } else {
54            Err(meta.error("unsupported attribute"))
55        }
56    });
57    parse_macro_input!(args with args_parser);
58
59    let item_fn: ItemFn = parse_macro_input!(item);
60    let attrs = &item_fn.attrs;
61    let vis = &item_fn.vis;
62    let sig = &item_fn.sig;
63    let ident = &sig.ident;
64
65    let mut new_sig = sig.clone();
66
67    let mut args = Vec::with_capacity(sig.inputs.len());
68    for arg in &sig.inputs {
69        let arg = match arg {
70            FnArg::Typed(arg) => arg,
71            FnArg::Receiver(_arg) => {
72                return error(arg, "associated methods not supported currently")
73            }
74        };
75        let ident = match arg.pat.as_ref() {
76            Pat::Ident(ident) => ident,
77            _ => {
78                return error(arg, "pattern not supported currently");
79            }
80        };
81        args.push(&ident.ident);
82    }
83
84    let call_wrap_unsafe = sig.unsafety.is_some() && (ident.to_string() == "main");
85    let call = if call_wrap_unsafe {
86        new_sig.unsafety = None;
87
88        quote_spanned! { sig.span() =>
89            unsafe {
90                #ident(
91                    #(#args),*
92                )
93            }
94        }
95    } else {
96        quote_spanned! { sig.span() =>
97            #ident(
98                #(#args),*
99            )
100        }
101    };
102
103    let set_version_str = version.map(|version| {
104        quote_spanned! { version.span() =>
105            builder.set_version_str(#version);
106        }
107    });
108
109    quote_spanned! { sig.span() =>
110        #(#attrs)*
111        #vis #new_sig {
112            let __gl_headless_ctx = {
113                #[allow(unused_mut)]
114                let mut builder = ::gl_headless::_internals::GLContextBuilder::new();
115                #set_version_str
116                builder.build().unwrap()
117            };
118            #item_fn
119            #call
120        }
121    }
122    .into()
123}
124
125#[must_use]
126fn error<T: ToTokens, U: fmt::Display>(tokens: T, message: U) -> TokenStream {
127    syn::Error::new_spanned(tokens, message)
128        .into_compile_error()
129        .into()
130}