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((|| generate_r7rs(&read_source_file(input)?))()).into()
91}
92
93fn generate_r7rs(source: &str) -> Result<proc_macro2::TokenStream, Box<dyn Error>> {
94    generate_scheme(source, |source, target| {
95        stak_compiler::compile_r7rs(source, target)
96    })
97}
98
99/// Compiles a module in Scheme into bytecodes with only built-ins.
100///
101/// # Examples
102///
103/// ```rust
104/// const BYTECODE: &[u8] = stak_macro::compile_bare!("($$define x 42)");
105/// ```
106#[proc_macro]
107pub fn compile_bare(input: TokenStream) -> TokenStream {
108    let input = parse_macro_input!(input as LitStr);
109
110    convert_result(generate_bare(&input.value())).into()
111}
112
113/// Includes a module in Scheme as bytecodes with only built-ins.
114///
115/// # Examples
116///
117/// ```rust
118/// const BYTECODE: &[u8] = stak_macro::include_bare!("foo.scm");
119/// ```
120#[proc_macro]
121pub fn include_bare(input: TokenStream) -> TokenStream {
122    let input = parse_macro_input!(input as LitStr);
123
124    convert_result((|| generate_bare(&read_source_file(input)?))()).into()
125}
126
127fn generate_bare(source: &str) -> Result<proc_macro2::TokenStream, Box<dyn Error>> {
128    generate_scheme(source, |source, target| {
129        stak_compiler::compile_bare(source, target)
130    })
131}
132
133fn generate_scheme(
134    source: &str,
135    compile: fn(&[u8], &mut Vec<u8>) -> Result<(), CompileError>,
136) -> Result<proc_macro2::TokenStream, Box<dyn Error>> {
137    let mut target = vec![];
138
139    compile(source.as_bytes(), &mut target)?;
140
141    let target = Literal::byte_string(&target);
142
143    Ok(quote! { #target })
144}