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_BYTECODES: &[u8] = include_bytes!(env!("STAK_BYTECODE_FILE"));
18
19/// Compiles a program in R7RS Scheme into bytecodes.
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 bytecodes 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    // TODO Add a heap size option.
45    let mut heap = vec![Default::default(); DEFAULT_HEAP_SIZE];
46    let mut error_message = vec![];
47    let device = ReadWriteDevice::new(source, target, &mut error_message);
48    let mut vm = Vm::new(
49        &mut heap,
50        SmallPrimitiveSet::new(
51            device,
52            VoidFileSystem::new(),
53            VoidProcessContext::new(),
54            VoidClock::new(),
55        ),
56    )?;
57
58    vm.initialize(COMPILER_BYTECODES.iter().copied())?;
59
60    vm.run().map_err(|error| {
61        if error_message.is_empty() {
62            CompileError::Run(error)
63        } else {
64            CompileError::User(String::from_utf8_lossy(&error_message).into_owned())
65        }
66    })?;
67
68    Ok(())
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use indoc::indoc;
75
76    mod bare {
77        use super::*;
78
79        #[test]
80        fn compile_nothing() {
81            compile_bare(b"".as_slice(), &mut vec![]).unwrap();
82        }
83
84        #[test]
85        fn compile_define() {
86            compile_bare(b"($$define x 42)".as_slice(), &mut vec![]).unwrap();
87        }
88    }
89
90    mod r7rs {
91        use super::*;
92
93        #[test]
94        fn compile_nothing() {
95            compile_r7rs(b"".as_slice(), &mut vec![]).unwrap();
96        }
97
98        #[test]
99        fn compile_define() {
100            compile_r7rs(b"(define x 42)".as_slice(), &mut vec![]).unwrap();
101        }
102
103        #[test]
104        fn compile_invalid_macro_call() {
105            let Err(CompileError::User(message)) = compile_r7rs(
106                indoc!(
107                    r#"
108                    (import (scheme base))
109
110                    (define-syntax foo
111                        (syntax-rules ()
112                            ((_)
113                                #f)))
114
115                    (foo 42)
116                    "#
117                )
118                .as_bytes(),
119                &mut vec![],
120            ) else {
121                panic!()
122            };
123
124            assert!(message.contains("invalid syntax"));
125        }
126
127        #[test]
128        fn compile_write_library() {
129            compile_r7rs(b"(import (scheme write))".as_slice(), &mut vec![]).unwrap();
130        }
131    }
132}