stak_compiler/
lib.rs

1//! Stak Scheme bytecode compiler.
2
3mod error;
4
5pub use self::error::CompileError;
6use core::env;
7use stak_configuration::DEFAULT_HEAP_SIZE;
8use stak_device::ReadWriteDevice;
9use stak_file::VoidFileSystem;
10use stak_process_context::VoidProcessContext;
11use stak_r7rs::SmallPrimitiveSet;
12use stak_time::VoidClock;
13use stak_vm::Vm;
14use std::io::{Read, Write};
15
16const PRELUDE_SOURCE: &str = include_str!("prelude.scm");
17const COMPILER_BYTECODE: &[u8] = include_bytes!(env!("STAK_BYTECODE_FILE"));
18
19/// Compiles a program in R7RS Scheme into bytecode.
20///
21/// # Examples
22///
23/// ```rust
24/// let source = "(define x 42)";
25/// let mut target = vec![];
26///
27/// stak_compiler::compile_r7rs(source.as_bytes(), &mut target).unwrap();
28/// ```
29pub fn compile_r7rs(source: impl Read, target: impl Write) -> Result<(), CompileError> {
30    compile_bare(PRELUDE_SOURCE.as_bytes().chain(source), target)
31}
32
33/// Compiles a program in Scheme into bytecode with only built-ins.
34///
35/// # Examples
36///
37/// ```rust
38/// let source = "($$define x 42)";
39/// let mut target = vec![];
40///
41/// stak_compiler::compile_bare(source.as_bytes(), &mut target).unwrap();
42/// ```
43pub fn compile_bare(source: impl Read, target: impl Write) -> Result<(), CompileError> {
44    let mut error_message = vec![];
45    let device = ReadWriteDevice::new(source, target, &mut error_message);
46    Vm::new(
47        // TODO Add a heap size option.
48        vec![Default::default(); DEFAULT_HEAP_SIZE],
49        SmallPrimitiveSet::new(
50            device,
51            VoidFileSystem::new(),
52            VoidProcessContext::new(),
53            VoidClock::new(),
54        ),
55    )?
56    .run(COMPILER_BYTECODE.iter().copied())
57    .map_err(|error| {
58        if error_message.is_empty() {
59            CompileError::Run(error)
60        } else {
61            CompileError::User(String::from_utf8_lossy(&error_message).into_owned())
62        }
63    })?;
64
65    Ok(())
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use indoc::indoc;
72
73    mod bare {
74        use super::*;
75
76        #[test]
77        fn compile_nothing() {
78            compile_bare(b"".as_slice(), &mut vec![]).unwrap();
79        }
80
81        #[test]
82        fn compile_define() {
83            compile_bare(b"($$define x 42)".as_slice(), &mut vec![]).unwrap();
84        }
85    }
86
87    mod r7rs {
88        use super::*;
89
90        #[test]
91        fn compile_nothing() {
92            compile_r7rs(b"".as_slice(), &mut vec![]).unwrap();
93        }
94
95        #[test]
96        fn compile_define() {
97            compile_r7rs(b"(define x 42)".as_slice(), &mut vec![]).unwrap();
98        }
99
100        #[test]
101        fn compile_invalid_macro_call() {
102            let Err(CompileError::User(message)) = compile_r7rs(
103                indoc!(
104                    r#"
105                    (import (scheme base))
106
107                    (define-syntax foo
108                        (syntax-rules ()
109                            ((_)
110                                #f)))
111
112                    (foo 42)
113                    "#
114                )
115                .as_bytes(),
116                &mut vec![],
117            ) else {
118                panic!()
119            };
120
121            assert!(message.contains("invalid syntax"));
122        }
123
124        #[test]
125        fn compile_write_library() {
126            compile_r7rs(b"(import (scheme write))".as_slice(), &mut vec![]).unwrap();
127        }
128    }
129}