stak_compiler/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Stak Scheme bytecode compiler.

mod error;

pub use self::error::CompileError;
use core::env;
use stak_configuration::DEFAULT_HEAP_SIZE;
use stak_device::ReadWriteDevice;
use stak_file::VoidFileSystem;
use stak_process_context::VoidProcessContext;
use stak_r7rs::SmallPrimitiveSet;
use stak_time::VoidClock;
use stak_vm::Vm;
use std::io::{Read, Write};

const PRELUDE_SOURCE: &str = include_str!("prelude.scm");
const COMPILER_BYTECODES: &[u8] = include_bytes!(env!("STAK_BYTECODE_FILE"));

/// Compiles a program in R7RS Scheme into bytecodes.
///
/// # Examples
///
/// ```rust
/// let source = "(define x 42)";
/// let mut target = vec![];
///
/// stak_compiler::compile_r7rs(source.as_bytes(), &mut target).unwrap();
/// ```
pub fn compile_r7rs(source: impl Read, target: impl Write) -> Result<(), CompileError> {
    compile_bare(PRELUDE_SOURCE.as_bytes().chain(source), target)
}

/// Compiles a program in Scheme into bytecodes with only built-ins.
///
/// # Examples
///
/// ```rust
/// let source = "($$define x 42)";
/// let mut target = vec![];
///
/// stak_compiler::compile_bare(source.as_bytes(), &mut target).unwrap();
/// ```
pub fn compile_bare(source: impl Read, target: impl Write) -> Result<(), CompileError> {
    // TODO Add a heap size option.
    let mut heap = vec![Default::default(); DEFAULT_HEAP_SIZE];
    let mut error_message = vec![];
    let device = ReadWriteDevice::new(source, target, &mut error_message);
    let mut vm = Vm::new(
        &mut heap,
        SmallPrimitiveSet::new(
            device,
            VoidFileSystem::new(),
            VoidProcessContext::new(),
            VoidClock::new(),
        ),
    )?;

    vm.initialize(COMPILER_BYTECODES.iter().copied())?;

    vm.run().map_err(|error| {
        if error_message.is_empty() {
            CompileError::Run(error)
        } else {
            CompileError::User(String::from_utf8_lossy(&error_message).into_owned())
        }
    })?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use indoc::indoc;

    mod bare {
        use super::*;

        #[test]
        fn compile_nothing() {
            compile_bare(b"".as_slice(), &mut vec![]).unwrap();
        }

        #[test]
        fn compile_define() {
            compile_bare(b"($$define x 42)".as_slice(), &mut vec![]).unwrap();
        }
    }

    mod r7rs {
        use super::*;

        #[test]
        fn compile_nothing() {
            compile_r7rs(b"".as_slice(), &mut vec![]).unwrap();
        }

        #[test]
        fn compile_define() {
            compile_r7rs(b"(define x 42)".as_slice(), &mut vec![]).unwrap();
        }

        #[test]
        fn compile_invalid_macro_call() {
            let Err(CompileError::User(message)) = compile_r7rs(
                indoc!(
                    r#"
                    (import (scheme base))

                    (define-syntax foo
                        (syntax-rules ()
                            ((_)
                                #f)))

                    (foo 42)
                    "#
                )
                .as_bytes(),
                &mut vec![],
            ) else {
                panic!()
            };

            assert!(message.contains("invalid syntax"));
        }

        #[test]
        fn compile_write_library() {
            compile_r7rs(b"(import (scheme write))".as_slice(), &mut vec![]).unwrap();
        }
    }
}