stak_macro/
lib.rs

1//! Macros to bundle and use Scheme programs.
2
3use cfg_elif::expr::feature;
4use core::error::Error;
5use proc_macro::TokenStream;
6use proc_macro2::Literal;
7use quote::{ToTokens, quote};
8use stak_compiler::CompileError;
9use stak_macro_util::{convert_result, read_source_file};
10use std::path::{MAIN_SEPARATOR_STR, Path};
11use syn::{Ident, LitStr, Token, parse::Parse, parse_macro_input};
12
13struct IncludeModuleInput {
14    path: LitStr,
15    module: Option<Ident>,
16}
17
18impl Parse for IncludeModuleInput {
19    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
20        let path = input.parse()?;
21        let mut module = None;
22
23        if input.parse::<Option<Token![,]>>()?.is_some() {
24            if let Some(value) = input.parse()? {
25                input.parse::<Option<Token![,]>>()?;
26                module = Some(value);
27            }
28        };
29
30        Ok(Self { path, module })
31    }
32}
33
34/// Includes bytecodes of a R7RS Scheme module built by the
35/// [`stak_build`](https://docs.rs/stak-build) crate.
36///
37/// See the [`stak`](https://docs.rs/stak) crate's documentation for full examples.
38#[proc_macro]
39pub fn include_module(input: TokenStream) -> TokenStream {
40    let input = parse_macro_input!(input as IncludeModuleInput);
41
42    convert_result(include_result(&input)).into()
43}
44
45fn include_result(input: &IncludeModuleInput) -> Result<proc_macro2::TokenStream, Box<dyn Error>> {
46    let path = format!("{}", Path::new("src").join(input.path.value()).display());
47    let full_path = quote!(concat!(env!("OUT_DIR"), #MAIN_SEPARATOR_STR, #path));
48    let module = input
49        .module
50        .as_ref()
51        .map_or_else(|| quote!(stak::module), |module| module.to_token_stream());
52
53    Ok(feature!(if ("hot-reload") {
54        quote! {
55            {
56                static MODULE: #module::HotReloadModule = #module::HotReloadModule::new(#full_path);
57                #module::UniversalModule::HotReload(&MODULE)
58            }
59        }
60    } else {
61        quote!(#module::UniversalModule::Static(#module::StaticModule::new(include_bytes!(#full_path))))
62    }))
63}
64
65/// Compiles a module in R7RS Scheme into bytecodes.
66///
67/// # Examples
68///
69/// ```rust
70/// const BYTECODE: &[u8] = stak_macro::compile_r7rs!("(define x 42)");
71/// ```
72#[proc_macro]
73pub fn compile_r7rs(input: TokenStream) -> TokenStream {
74    let input = parse_macro_input!(input as LitStr);
75
76    convert_result(generate_r7rs(&input.value())).into()
77}
78
79/// Includes a module in R7RS Scheme as bytecodes.
80///
81/// # Examples
82///
83/// ```rust
84/// const BYTECODE: &[u8] = stak_macro::include_r7rs!("foo.scm");
85/// ```
86#[proc_macro]
87pub fn include_r7rs(input: TokenStream) -> TokenStream {
88    let input = parse_macro_input!(input as LitStr);
89
90    convert_result((|| {
91        let source = generate_r7rs(&read_source_file(input.clone())?)?;
92
93        Ok(quote!({
94            // Detect source changes.
95            static _SOURCE: &str = include_str!(#input);
96            #source
97        }))
98    })())
99    .into()
100}
101
102fn generate_r7rs(source: &str) -> Result<proc_macro2::TokenStream, Box<dyn Error>> {
103    generate_scheme(source, |source, target| {
104        stak_compiler::compile_r7rs(source, target)
105    })
106}
107
108/// Compiles a module in Scheme into bytecodes with only built-ins.
109///
110/// # Examples
111///
112/// ```rust
113/// const BYTECODE: &[u8] = stak_macro::compile_bare!("($$define x 42)");
114/// ```
115#[proc_macro]
116pub fn compile_bare(input: TokenStream) -> TokenStream {
117    let input = parse_macro_input!(input as LitStr);
118
119    convert_result(generate_bare(&input.value())).into()
120}
121
122/// Includes a module in Scheme as bytecodes with only built-ins.
123///
124/// # Examples
125///
126/// ```rust
127/// const BYTECODE: &[u8] = stak_macro::include_bare!("foo.scm");
128/// ```
129#[proc_macro]
130pub fn include_bare(input: TokenStream) -> TokenStream {
131    let input = parse_macro_input!(input as LitStr);
132
133    convert_result((|| generate_bare(&read_source_file(input)?))()).into()
134}
135
136fn generate_bare(source: &str) -> Result<proc_macro2::TokenStream, Box<dyn Error>> {
137    generate_scheme(source, |source, target| {
138        stak_compiler::compile_bare(source, target)
139    })
140}
141
142fn generate_scheme(
143    source: &str,
144    compile: fn(&[u8], &mut Vec<u8>) -> Result<(), CompileError>,
145) -> Result<proc_macro2::TokenStream, Box<dyn Error>> {
146    let mut target = vec![];
147
148    compile(source.as_bytes(), &mut target)?;
149
150    let target = Literal::byte_string(&target);
151
152    Ok(quote! { #target })
153}