euphony_compiler/
lib.rs

1use std::{io, sync::Arc};
2
3macro_rules! error {
4    ($fmt:literal $($tt:tt)*) => {
5        ::std::io::Error::new(::std::io::ErrorKind::InvalidInput, format!($fmt $($tt)*))
6    };
7}
8
9pub trait Writer: Sync {
10    fn is_cached(&self, hash: &Hash) -> bool;
11    fn sink(&mut self, hash: &Hash) -> euphony_node::BoxProcessor;
12    fn group<I: Iterator<Item = Entry>>(&mut self, name: &str, hash: &Hash, entries: I);
13    fn buffer<F: FnOnce(Box<dyn BufferReader>) -> Result<Vec<ConvertedBuffer>, E>, E>(
14        &self,
15        path: &str,
16        sample_rate: u64,
17        init: F,
18    ) -> Result<Vec<CachedBuffer>, E>;
19}
20
21pub trait BufferReader: io::Read + Send + Sync + 'static {}
22
23impl<T: io::Read + Send + Sync + 'static> BufferReader for T {}
24
25pub type ConvertedBuffer = Vec<sample::DefaultSample>;
26
27#[derive(Clone, Debug)]
28pub struct CachedBuffer {
29    pub samples: Arc<[f64]>,
30    pub hash: Hash,
31}
32
33#[derive(Clone, Copy, Debug)]
34pub struct Entry {
35    pub sample_offset: u64,
36    pub hash: Hash,
37}
38
39#[path = "sample.rs"]
40mod internal_sample;
41pub mod sample {
42    pub(crate) use super::internal_sample::*;
43    pub use euphony_dsp::sample::*;
44}
45
46// TODO better error?
47pub type Error = std::io::Error;
48pub type Result<T = (), E = Error> = core::result::Result<T, E>;
49pub type Hash = [u8; 32];
50
51mod buffer;
52mod compiler;
53mod group;
54mod instruction;
55mod node;
56mod parallel;
57mod render;
58mod sink;
59
60#[derive(Debug, Default)]
61pub struct Compiler {
62    compiler: compiler::Compiler,
63    render: render::Renderer,
64}
65
66impl Compiler {
67    pub fn compile<I: io::Read, O: Writer>(&mut self, input: &mut I, output: &mut O) -> Result {
68        // clear everything out first
69        self.compiler.reset();
70        self.render.reset();
71
72        euphony_command::decode(input, &mut self.compiler)?;
73        let buffers = self.compiler.finalize(output)?;
74        self.render.set_buffers(buffers);
75
76        for instruction in self.compiler.instructions() {
77            self.render
78                .push(instruction, output)
79                .map_err(|err| error!("invalid instruction {:?}", err))?;
80        }
81
82        for (_id, group, entries) in self.compiler.groups() {
83            output.group(&group.name, &group.hash, entries);
84        }
85
86        Ok(())
87    }
88
89    pub fn display<I: io::Read, O: io::Write>(&mut self, input: &mut I, output: &mut O) -> Result {
90        // clear everything out first
91        self.compiler.reset();
92        self.render.reset();
93
94        euphony_command::decode(input, &mut self.compiler)?;
95
96        struct Output;
97
98        impl Writer for Output {
99            fn is_cached(&self, _hash: &Hash) -> bool {
100                false
101            }
102
103            fn sink(&mut self, _hash: &Hash) -> euphony_node::BoxProcessor {
104                unimplemented!()
105            }
106
107            fn group<I: Iterator<Item = Entry>>(&mut self, _name: &str, _hash: &Hash, _entries: I) {
108            }
109
110            fn buffer<F: FnOnce(Box<dyn BufferReader>) -> Result<Vec<ConvertedBuffer>, E>, E>(
111                &self,
112                _path: &str,
113                _sample_rate: u64,
114                _init: F,
115            ) -> Result<Vec<CachedBuffer>, E> {
116                unimplemented!()
117            }
118        }
119
120        self.compiler.finalize(&Output)?;
121
122        writeln!(output, "# Groups")?;
123        for (_id, group, entries) in self.compiler.groups() {
124            let count = entries.count();
125            writeln!(output, "* {:?} ({} entries)", group.name, count)?;
126        }
127        writeln!(output)?;
128
129        writeln!(output, "# Instructions")?;
130        for instruction in self.compiler.instructions() {
131            writeln!(output, "{}", instruction)?;
132        }
133
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use bolero::check;
142    use std::io::Cursor;
143
144    struct Output;
145
146    impl Writer for Output {
147        fn is_cached(&self, _hash: &Hash) -> bool {
148            false
149        }
150
151        fn sink(&mut self, _hash: &Hash) -> euphony_node::BoxProcessor {
152            // silence
153            euphony_dsp::nodes::load(106).unwrap()
154        }
155
156        fn group<I: Iterator<Item = Entry>>(&mut self, _name: &str, _hash: &Hash, entries: I) {
157            for _ in entries {}
158        }
159
160        fn buffer<F: FnOnce(Box<dyn BufferReader>) -> Result<Vec<ConvertedBuffer>, E>, E>(
161            &self,
162            _path: &str,
163            _sample_rate: u64,
164            _init: F,
165        ) -> Result<Vec<CachedBuffer>, E> {
166            Ok(vec![])
167        }
168    }
169
170    #[test]
171    fn fuzz() {
172        check!().for_each(|input| {
173            let mut compiler = Compiler::default();
174            let mut input = Cursor::new(input);
175            let _ = compiler.compile(&mut input, &mut Output);
176        });
177    }
178}