Include my code
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | /target | ||||||
|  | example.wav | ||||||
|  | .vscode/ | ||||||
							
								
								
									
										25
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | # This file is automatically @generated by Cargo. | ||||||
|  | # It is not intended for manual editing. | ||||||
|  | version = 3 | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "lansky" | ||||||
|  | version = "0.1.0" | ||||||
|  | dependencies = [ | ||||||
|  |  "wav", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "riff" | ||||||
|  | version = "1.0.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "wav" | ||||||
|  | version = "1.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "a65e199c799848b4f997072aa4d673c034f80f40191f97fe2f0a23f410be1609" | ||||||
|  | dependencies = [ | ||||||
|  |  "riff", | ||||||
|  | ] | ||||||
							
								
								
									
										9
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | [package] | ||||||
|  | name = "lansky" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2021" | ||||||
|  |  | ||||||
|  | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | wav = "1.0.0" | ||||||
							
								
								
									
										77
									
								
								asm_notes.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								asm_notes.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | NOTES: Slightly out of date -- I haven't added the Imm mov mode or the ix register yet | ||||||
|  |  | ||||||
|  | Registers: | ||||||
|  |     $a0..9: Corresponds to input 0 through 9 | ||||||
|  |     $l0..9: "Locals" 0..9 | ||||||
|  |     $z0..9: Corresponds to output 0 through 9 | ||||||
|  |     $r0..255: Auxiliary uint64_ts | ||||||
|  |  | ||||||
|  |     $ix: Index base | ||||||
|  |  | ||||||
|  | Channels: | ||||||
|  |     A channel is a vector of samples with a known "formal range" -- | ||||||
|  |     this formal range includes its start (relative to the whole song) | ||||||
|  |     and its basic length. | ||||||
|  |  | ||||||
|  |     You can use outputs that aren't exposed as part of your device.  | ||||||
|  |     This can be a good place to hide wavetables and stuff. | ||||||
|  |     They just won't be returned. | ||||||
|  |  | ||||||
|  | Memory model: | ||||||
|  |     $a0..9, $l0..9, and $z0..9 correspond to arrays | ||||||
|  |     When you read $a0, it reads channel a0 at $ix+a0.formal_start+a0.loop_start | ||||||
|  |     When you write $z0, it writes channel z0 at $ix+z0.formal_start+z0.loop_start | ||||||
|  |  | ||||||
|  |     Unknown positions are 0. | ||||||
|  |  | ||||||
|  |     A channel has an informal_start and an informal_length which initially match | ||||||
|  |     formal_start and formal_length. | ||||||
|  |  | ||||||
|  |     Writing to a position outside of informal_start..informal_start+informal_length  | ||||||
|  |     extends the bounds. | ||||||
|  |  | ||||||
|  |     In looped mode, then the ultimate indices are wrapped to  | ||||||
|  |     (formal_start+loop_start..formal_start+loop_start+loop_length)  | ||||||
|  |     and all other behavior remains the same as above. | ||||||
|  |  | ||||||
|  | Instructions: | ||||||
|  |     mov|add|xor ($l0..9, $z0..9, $r0..255), ($a0..9, $l0..9, $z0..9, $r0..255) | ||||||
|  |     Copy value, add value, or xor value. | ||||||
|  |  | ||||||
|  |     range ($a0..9, $l0..9, $z0..9), loop_start, loop_length | ||||||
|  |     Start and length are immediate values. | ||||||
|  |     If length is nonzero, the channel is in "looped" mode.  | ||||||
|  |  | ||||||
|  |     inp ($a0..9, $l0..9, $z0..9) | ||||||
|  |     Push a copy of the channel to the stack for "invoke". | ||||||
|  |     The channel will be sliced: | ||||||
|  |     - if loop_start is zero and loop_length is zero:  | ||||||
|  |         The formal range runs from formal_start to formal_start+formal_length. | ||||||
|  |         The informal range runs from informal_start to informal_start+informal_length. | ||||||
|  |     - if loop_start is nonzero and loop_length is zero:  | ||||||
|  |         The formal range runs from formal_start+loop_start to formal_start+formal_length | ||||||
|  |         The informal range runs from formal_start+loop_start to informal_start+informal_length | ||||||
|  |     - if loop_length is nonzero:  | ||||||
|  |         The formal range runs from formal_start+loop_start to formal_start+loop_start+loop_length | ||||||
|  |         The informal range is the same as the formal range. | ||||||
|  |  | ||||||
|  |     dup | ||||||
|  |     Duplicate an item from the invoke stack. | ||||||
|  |  | ||||||
|  |     oup (mov|add|xor) ($l0..9, $z0..9) | ||||||
|  |     Pop an output from the invoke stack and write it to an output channel or local. | ||||||
|  |  | ||||||
|  |     All indexes from informal_start to informal_start+informal_length are written. | ||||||
|  |  | ||||||
|  |     The instruction used to write can be mov, add, or xor | ||||||
|  |  | ||||||
|  |     invoke device, n_inputs, n_outputs | ||||||
|  |     Invoke the device by name (an immediate string) | ||||||
|  |     Asserts that the device takes n_inputs inputs (an immediate integer) and n_outputs outputs (an immediate integer) | ||||||
|  |     Asserts that there are actually n_inputs inputs on the stack. | ||||||
|  |     After invoke, there will be n_outputs outputs on the stack. | ||||||
|  |     They will be ordered such that the first call to oup produces the first output, the second produces the second,  | ||||||
|  |     and so on | ||||||
|  |  | ||||||
|  |     exit | ||||||
|  |     Exit the current procedure. | ||||||
							
								
								
									
										131
									
								
								src/audio.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/audio.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | |||||||
|  | use std::ops::{Range, Index, IndexMut}; | ||||||
|  |  | ||||||
|  | // Fixed point: 0xFFFF_FFFF.FFFF_FFFF | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub struct Sample(pub i64); | ||||||
|  |  | ||||||
|  | impl Sample { | ||||||
|  |     pub fn to_i16_sample(&self) -> i16 { | ||||||
|  |         // interpret self as fixed-point | ||||||
|  |         if self.0 > i32::MAX as i64 { return i16::MAX } | ||||||
|  |         if self.0 < i32::MIN as i64 { return i16::MIN } | ||||||
|  |         let low_part = self.0 as i32; | ||||||
|  |         return (low_part >> 16) as i16 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Default for Sample { | ||||||
|  |     fn default() -> Self { BLANK_SAMPLE } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const BLANK_SAMPLE: Sample = Sample(0); | ||||||
|  |  | ||||||
|  | #[derive(Clone)] | ||||||
|  | pub struct Channel { | ||||||
|  |     formal: Range<i32>, | ||||||
|  |  | ||||||
|  |     minus: Vec<Sample>, | ||||||
|  |     plus: Vec<Sample>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct Bounds { | ||||||
|  |     pub formal: Range<i32>, | ||||||
|  |     pub informal: Range<i32>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Channel { | ||||||
|  |     pub fn new(formal: Range<i32>) -> Self { | ||||||
|  |         assert!(formal.end >= formal.start); | ||||||
|  |         Channel { | ||||||
|  |             formal: formal, | ||||||
|  |             minus: vec![], | ||||||
|  |             plus: vec![], | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn formal(&self) -> Range<i32> { | ||||||
|  |         self.formal.clone() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn bounds(&self) -> Bounds { | ||||||
|  |         let formal = self.formal.clone(); | ||||||
|  |         let informal_start = formal.start - self.minus.len() as i32; | ||||||
|  |         let informal_len_0 = self.minus.len() + self.plus.len(); | ||||||
|  |         let informal_len_1 = (formal.end - informal_start) as usize;  | ||||||
|  |         let informal_len = informal_len_0.max(informal_len_1); | ||||||
|  |  | ||||||
|  |         return Bounds { | ||||||
|  |             formal, | ||||||
|  |             informal: informal_start..(informal_start + informal_len as i32) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn blank() -> Channel { | ||||||
|  |         return Channel::new(0..0); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn slice(&self, start: i32, length: i32) -> Channel { | ||||||
|  |         if start == 0 && length == 0 { | ||||||
|  |             return self.clone() | ||||||
|  |         } | ||||||
|  |         if start != 0 && length == 0 { | ||||||
|  |             let formal_start = self.formal.start + start; | ||||||
|  |             let mut formal_end = self.formal.start + self.formal.end; | ||||||
|  |             if formal_end < formal_start { formal_end = formal_start; } | ||||||
|  |  | ||||||
|  |             let mut chan = Channel::new(formal_start..formal_end); | ||||||
|  |             for i in 0..self.plus.len() { | ||||||
|  |                 chan[self.formal.start + i as i32] = self[self.formal.start + i as i32] | ||||||
|  |             } | ||||||
|  |             return chan | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let formal_start = self.formal.start + start; | ||||||
|  |         let formal_end = self.formal.start + start + length; | ||||||
|  |         let mut chan = Channel::new(formal_start..formal_end); | ||||||
|  |         for i in formal_start..formal_end { | ||||||
|  |             chan[i] = self[i]; | ||||||
|  |         } | ||||||
|  |         return chan | ||||||
|  |     } | ||||||
|  | }  | ||||||
|  |  | ||||||
|  | impl Index<i32> for Channel { | ||||||
|  |     type Output = Sample; | ||||||
|  |  | ||||||
|  |     fn index(&self, index: i32) -> &Self::Output { | ||||||
|  |         let adj_idx = index - self.formal.start; | ||||||
|  |         if adj_idx < 0 { | ||||||
|  |             let neg_adj_idx = (-adj_idx) as usize - 1; | ||||||
|  |             self.minus.get(neg_adj_idx).unwrap_or(&BLANK_SAMPLE) | ||||||
|  |         } else { | ||||||
|  |             let pos_adj_idx = adj_idx as usize; | ||||||
|  |             self.plus.get(pos_adj_idx).unwrap_or(&BLANK_SAMPLE) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl IndexMut<i32> for Channel { | ||||||
|  |     fn index_mut(&mut self, index: i32) -> &mut Self::Output { | ||||||
|  |         let adj_idx = index - self.formal.start; | ||||||
|  |         if adj_idx < 0 { | ||||||
|  |             let neg_adj_idx = (-adj_idx) as usize - 1; | ||||||
|  |             if self.minus.len() < neg_adj_idx + 1 { | ||||||
|  |                 self.minus.reserve(neg_adj_idx + 1); | ||||||
|  |                 while self.minus.len() < neg_adj_idx + 1 { | ||||||
|  |                     self.minus.push(BLANK_SAMPLE); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return &mut self.minus[neg_adj_idx] | ||||||
|  |         } else { | ||||||
|  |             let pos_adj_idx = adj_idx as usize; | ||||||
|  |             if self.plus.len() < pos_adj_idx + 1{ | ||||||
|  |                 self.plus.reserve(pos_adj_idx + 1); | ||||||
|  |                 while self.plus.len() < pos_adj_idx + 1 { | ||||||
|  |                     self.plus.push(BLANK_SAMPLE); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return &mut self.plus[pos_adj_idx] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										122
									
								
								src/device.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/device.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | use std::{collections::HashMap, cell::Cell}; | ||||||
|  |  | ||||||
|  | use crate::{audio::Channel, error::{Lansky, LanskyError}, vm::{Procedure, Block}, rack::Rack}; | ||||||
|  |  | ||||||
|  | pub struct Device { | ||||||
|  |     proto: Proto, | ||||||
|  |     implementation: Implementation, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone)] | ||||||
|  | pub struct Proto {   | ||||||
|  |     pub name: String, | ||||||
|  |     pub inputs: Vec<String>, | ||||||
|  |     pub outputs: Vec<String>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | enum Implementation { | ||||||
|  |     Native(fn(Vec<Channel>) -> Lansky<Vec<Channel>>), | ||||||
|  |     Hosted(Procedure), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Device { | ||||||
|  |     pub fn native( | ||||||
|  |         name: String,  | ||||||
|  |         inputs: Vec<String>,  | ||||||
|  |         outputs: Vec<String>,  | ||||||
|  |         native: fn(Vec<Channel>) -> Lansky<Vec<Channel>> | ||||||
|  |     ) -> Lansky<Self> { | ||||||
|  |         let proto = Proto { name, inputs, outputs }; | ||||||
|  |         Self::new(proto, Implementation::Native(native)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn hosted( | ||||||
|  |         name: String,  | ||||||
|  |         inputs: Vec<String>,  | ||||||
|  |         outputs: Vec<String>,  | ||||||
|  |         blocks: Vec<Block>, | ||||||
|  |     ) -> Lansky<Self> { | ||||||
|  |         let proto = Proto { name, inputs, outputs }; | ||||||
|  |         Self::new(proto.clone(), Implementation::Hosted(Procedure::new(proto, blocks))) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn new(proto: Proto, implementation: Implementation) -> Lansky<Self> { | ||||||
|  |         for i0 in 0..proto.inputs.len() { | ||||||
|  |             for i1 in i0+1..proto.inputs.len() { | ||||||
|  |                 if proto.inputs[i0] == proto.inputs[i1] {  | ||||||
|  |                     return Err(LanskyError::DuplicateInput { name: proto.inputs[i0].clone(), i0, i1 })  | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for i0 in 0..proto.outputs.len() { | ||||||
|  |             for i1 in i0+1..proto.outputs.len() { | ||||||
|  |                 if proto.outputs[i0] == proto.outputs[i1] {  | ||||||
|  |                     return Err(LanskyError::DuplicateOutput { name: proto.outputs[i0].clone(), i0, i1 })  | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // TODO: Validate the code in some way? | ||||||
|  |         // Not needed for native devices | ||||||
|  |  | ||||||
|  |         Ok(Device { proto, implementation }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn analyze(&self, rack: &Rack) -> Lansky<()> { | ||||||
|  |         match &self.implementation { | ||||||
|  |             Implementation::Native(_) => {} | ||||||
|  |             Implementation::Hosted(h) => { | ||||||
|  |                 h.analyze(rack)?; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn get_proto(&self) -> &Proto { | ||||||
|  |         &self.proto | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn invoke_named(&self, rack: &Rack, mut inputs: HashMap<String, Channel>) -> Lansky<HashMap<String, Channel>> { | ||||||
|  |         let expected = self.proto.inputs.len(); | ||||||
|  |         let got = inputs.len(); | ||||||
|  |         if expected != got { return Err(LanskyError::WrongNInputs { expected, got }) } | ||||||
|  |  | ||||||
|  |         let mut inputs_2 = Vec::with_capacity(self.proto.inputs.len()); | ||||||
|  |         for i in self.proto.inputs.iter() { | ||||||
|  |             let Some(channel_in) = inputs.remove(i) else { | ||||||
|  |                 return Err(LanskyError::MissingInput { name: i.clone() }) | ||||||
|  |             }; | ||||||
|  |             inputs_2.push(channel_in) | ||||||
|  |         } | ||||||
|  |         for i in inputs.keys() { | ||||||
|  |             if !self.proto.inputs.contains(i) { | ||||||
|  |                 return Err(LanskyError::UnknownInput { name: i.clone() }) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let outputs = self.invoke_positional(rack, inputs_2)?; | ||||||
|  |  | ||||||
|  |         let mut outputs_2 = HashMap::new(); | ||||||
|  |         for (k, v) in self.proto.outputs.iter().zip(outputs.into_iter()) { | ||||||
|  |             outputs_2.insert(k.clone(), v); | ||||||
|  |         } | ||||||
|  |         Ok(outputs_2) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn invoke_positional(&self, rack: &Rack, inputs: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||||
|  |         let expected = self.proto.inputs.len(); | ||||||
|  |         let got = inputs.len(); | ||||||
|  |         if expected != got { return Err(LanskyError::WrongNInputs { expected, got }) } | ||||||
|  |         let outputs = match &self.implementation { | ||||||
|  |             Implementation::Native(f) => f(inputs)?, | ||||||
|  |             Implementation::Hosted(procedure) => procedure.invoke(rack, inputs)?, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         let expected = self.proto.outputs.len(); | ||||||
|  |         let got = outputs.len(); | ||||||
|  |         if expected != got { return Err(LanskyError::WrongNOutputs { expected, got }) } | ||||||
|  |         Ok(outputs) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | pub type Lansky<T> = Result<T, LanskyError>; | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum LanskyError { | ||||||
|  |     DuplicateDevice { name: String }, | ||||||
|  |     UnknownDevice { name: String }, | ||||||
|  |  | ||||||
|  |     DuplicateInput { name: String, i0: usize, i1: usize }, | ||||||
|  |     DuplicateOutput { name: String, i0: usize, i1: usize }, | ||||||
|  |      | ||||||
|  |     MissingInput { name: String }, | ||||||
|  |     UnknownInput { name: String }, | ||||||
|  |  | ||||||
|  |     WrongNInputs { expected: usize, got: usize }, | ||||||
|  |     WrongNOutputs { expected: usize, got: usize }, | ||||||
|  |  | ||||||
|  |     NoFirstBlock,  | ||||||
|  |  | ||||||
|  |     WrongNStack { why: LanskyStackReason, expected: usize, got: usize }, | ||||||
|  |  | ||||||
|  |     MissingRegA { i: usize }, | ||||||
|  |     MissingRegZ { i: usize }, | ||||||
|  |  | ||||||
|  |     WrongNChannels { expected: usize, got: usize }, | ||||||
|  |  | ||||||
|  |     IO(std::io::Error), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum LanskyStackReason { | ||||||
|  |     CannotDupEmptyStack, | ||||||
|  |     CannotOupEmptyStack, | ||||||
|  |     CannotPopEmptyStack, | ||||||
|  |  | ||||||
|  |     BlockMustEndWithEmptyStack, | ||||||
|  |  | ||||||
|  |     InvokeNInputs | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | // TODOs: | ||||||
|  | // Generate at 88200Hz, downsample to 44100Hz | ||||||
|  | // Parser for assembly language | ||||||
|  | // More context in error messages | ||||||
|  | // Replace the sample type with something more Pico 8 fixedpoint-like (right now we are always falling down on the i64 interface) | ||||||
|  | // Use the _top_ part when coercing the sample type to an i32 | ||||||
|  | // jmp and similar instructions | ||||||
|  | // Use primarily the named interface and allow defaulting | ||||||
|  | // Channel-like objects that produce a constant value  | ||||||
|  |  | ||||||
|  | use std::{path::Path, fs::File}; | ||||||
|  |  | ||||||
|  | use audio::{Sample, Channel}; | ||||||
|  | use device::Device; | ||||||
|  | use error::{Lansky, LanskyError}; | ||||||
|  | use rack::Rack; | ||||||
|  | use vm::{Block, BodyInstruction, ExitInstruction, MovSrc, OupDst, RegZ}; | ||||||
|  |  | ||||||
|  | mod audio; | ||||||
|  | mod error; | ||||||
|  | mod device; | ||||||
|  | mod standard_devices; | ||||||
|  | mod rack; | ||||||
|  | mod vm; | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  |     main_err().expect("crash:"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | fn main_err() -> Lansky<()> { | ||||||
|  |     let mut rack = Rack::new(); | ||||||
|  |  | ||||||
|  |     rack.add_device(Device::hosted( | ||||||
|  |         "main".to_string(), | ||||||
|  |         vec![], | ||||||
|  |         vec!["audio".to_string()], | ||||||
|  |         vec![ | ||||||
|  |             Block { | ||||||
|  |                 name: "entry".to_string(), | ||||||
|  |                 body: vec![ | ||||||
|  |                     BodyInstruction::New(MovSrc::Imm(Sample(0)), MovSrc::Imm(Sample(100000)), MovSrc::Imm(Sample(440))), | ||||||
|  |                     BodyInstruction::Invoke("gensin".to_string(), 1, 1), | ||||||
|  |                     BodyInstruction::Pop(OupDst::Z(RegZ(0))), | ||||||
|  |                 ], | ||||||
|  |                 exit: ExitInstruction::Exit, | ||||||
|  |             } | ||||||
|  |         ], | ||||||
|  |     )?)?; | ||||||
|  |  | ||||||
|  |     rack.analyze_all()?; | ||||||
|  |  | ||||||
|  |     let result =  | ||||||
|  |         rack.invoke_positional("main", vec![])?; | ||||||
|  |  | ||||||
|  |     export(Path::new("example.wav"), result)?; | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn export(path: &Path, audio: Vec<Channel>) -> Lansky<()> { | ||||||
|  |     match &audio[..] { | ||||||
|  |         [mono] => { | ||||||
|  |             let header = wav::Header::new( | ||||||
|  |                 wav::header::WAV_FORMAT_PCM, | ||||||
|  |                 1, | ||||||
|  |                 44100, | ||||||
|  |                 16 | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             let mut out_file = File::create(path).map_err(|e| LanskyError::IO(e))?; | ||||||
|  |             let data = channel_to_data(&mono); | ||||||
|  |             wav::write(header, &data, &mut out_file).map_err(|e| LanskyError::IO(e))?; | ||||||
|  |  | ||||||
|  |             Ok(()) | ||||||
|  |         }, | ||||||
|  |         _ => { | ||||||
|  |             return Err(LanskyError::WrongNChannels { expected: 1, got: audio.len() }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn channel_to_data(channel: &Channel) -> wav::BitDepth { | ||||||
|  |     let mut v: Vec<i16> = vec![]; | ||||||
|  |     for i in channel.bounds().informal { | ||||||
|  |         v.push(channel[i].to_i16_sample()) | ||||||
|  |     } | ||||||
|  |     wav::BitDepth::Sixteen(v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | fn main_err() -> std::io::Result<()> { | ||||||
|  |     let mut out_file = File::create(Path::new("output.wav"))?; | ||||||
|  |     wav::write(header, &data, &mut out_file)?; | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | */ | ||||||
							
								
								
									
										56
									
								
								src/rack.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/rack.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | use std::collections::HashMap; | ||||||
|  |  | ||||||
|  | use crate::{device::{Device, Proto}, error::{Lansky, LanskyError}, standard_devices, audio::Channel}; | ||||||
|  |  | ||||||
|  | pub struct Rack { | ||||||
|  |     devices: HashMap<String, Device> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Rack { | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         let mut r = Rack { | ||||||
|  |             devices: HashMap::new() | ||||||
|  |         }; | ||||||
|  |         standard_devices::augment(&mut r); | ||||||
|  |         r | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn analyze_all(&self) -> Lansky<()> { | ||||||
|  |         for d in self.devices.values() { | ||||||
|  |             d.analyze(self)?; | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn add_device(&mut self, device: Device) -> Lansky<()> { | ||||||
|  |         if self.devices.contains_key(&device.get_proto().name) { | ||||||
|  |             return Err(LanskyError::DuplicateDevice { name: device.get_proto().name.clone() }) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let name = device.get_proto().name.clone(); | ||||||
|  |         self.devices.insert(name, device); | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn get_device(&self, name: &str) -> Lansky<&Device> { | ||||||
|  |         let Some(device) = self.devices.get(name) else { | ||||||
|  |             return Err(LanskyError::UnknownDevice { name: name.to_string() }) | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         Ok(device) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn invoke_named(&self, name: &str, inputs: HashMap<String, Channel>) -> Lansky<HashMap<String, Channel>> { | ||||||
|  |         self.get_device(name)?.invoke_named(self, inputs) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn invoke_positional(&self, name: &str, inputs: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||||
|  |         self.get_device(name)?.invoke_positional(self, inputs) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn get_proto(&self, name: &str) -> Lansky<&Proto> { | ||||||
|  |         Ok(self.get_device(name)?.get_proto()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								src/standard_devices/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/standard_devices/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | use std::f32::consts::PI; | ||||||
|  |  | ||||||
|  | use crate::{rack::Rack, device::Device, audio::{Channel, Sample}, error::Lansky}; | ||||||
|  |  | ||||||
|  | pub fn augment(r: &mut Rack) -> Lansky<()> { | ||||||
|  |     r.add_device(Device::native( | ||||||
|  |         "gensin".to_string(),  | ||||||
|  |         vec!["frequency".to_string()], | ||||||
|  |         vec!["signal".to_string()], | ||||||
|  |         gensin | ||||||
|  |     )?)?; | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn gensin(mut chans: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||||
|  |     assert!(chans.len() == 1); | ||||||
|  |     let inp = chans.pop().expect("must be one channel"); | ||||||
|  |  | ||||||
|  |     let mut pos = 0.0; | ||||||
|  |     let sr = 44100.0; | ||||||
|  |  | ||||||
|  |     let mut oup = Channel::new(inp.formal()); | ||||||
|  |  | ||||||
|  |     for i in inp.bounds().informal { | ||||||
|  |         let freq = inp[i].0 as f32; | ||||||
|  |         pos += freq/sr; | ||||||
|  |         pos %= 1.0; | ||||||
|  |         oup[i] = Sample(((pos * PI * 2.0).sin() * i32::MAX as f32) as i64); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Ok(vec![oup]) | ||||||
|  | } | ||||||
							
								
								
									
										519
									
								
								src/vm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								src/vm.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,519 @@ | |||||||
|  | use std::cell::Cell; | ||||||
|  |  | ||||||
|  | use crate::{audio::{Channel, Sample}, error::{Lansky, LanskyError, LanskyStackReason}, device::Proto, rack::Rack}; | ||||||
|  |  | ||||||
|  | pub(crate) struct Procedure { | ||||||
|  |     proto: Proto, | ||||||
|  |     blocks: Vec<Block>, | ||||||
|  |     analysis: Cell<Option<Analysis>>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | struct Analysis { | ||||||
|  |     stack_depth: usize, | ||||||
|  |  | ||||||
|  |     proto_n_inputs: usize, | ||||||
|  |     proto_n_outputs: usize, | ||||||
|  |  | ||||||
|  |     n_reg_ls: usize, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct Block { | ||||||
|  |     pub name: String, | ||||||
|  |     pub body: Vec<BodyInstruction>, | ||||||
|  |     pub exit: ExitInstruction | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub enum BodyInstruction { | ||||||
|  |     Mov(MovType, MovDst, MovSrc), | ||||||
|  |     Range(RangeDst, i32, i32), | ||||||
|  |     Inp(InpSrc), | ||||||
|  |     Dup(), | ||||||
|  |     Oup(MovType, OupDst), | ||||||
|  |     Pop(OupDst), | ||||||
|  |     New(MovSrc, MovSrc, MovSrc), | ||||||
|  |     Invoke(String, usize, usize), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub enum ExitInstruction { | ||||||
|  |     Exit, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub enum MovType { Mov, Add, Xor } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] pub enum MovDst { L(RegL), R(RegR), Z(RegZ), Ix } | ||||||
|  | #[derive(Clone, Copy)] pub enum MovSrc { A(RegA), L(RegL), R(RegR), Z(RegZ), Ix, Imm(Sample) } | ||||||
|  | #[derive(Clone, Copy)] pub enum RangeDst { A(RegA), L(RegL), Z(RegZ) } | ||||||
|  | #[derive(Clone, Copy)] pub enum InpSrc { A(RegA), L(RegL), Z(RegZ) } | ||||||
|  | #[derive(Clone, Copy)] pub enum OupDst { L(RegL), Z(RegZ) } | ||||||
|  |  | ||||||
|  | impl OupDst { | ||||||
|  |     fn to_mov_dst(self) -> MovDst { | ||||||
|  |         match self { | ||||||
|  |             Self::L(l) => MovDst::L(l), | ||||||
|  |             Self::Z(z) => MovDst::Z(z), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] pub struct RegA(pub u8); | ||||||
|  | #[derive(Clone, Copy)] pub struct RegL(pub u8); | ||||||
|  | #[derive(Clone, Copy)] pub struct RegR(pub u8); | ||||||
|  | #[derive(Clone, Copy)] pub struct RegZ(pub u8); | ||||||
|  |  | ||||||
|  | struct Scope { | ||||||
|  |     ix: Sample, | ||||||
|  |  | ||||||
|  |     reg_as: Vec<Channel>, | ||||||
|  |     reg_ls: Vec<Channel>, | ||||||
|  |     reg_rs: [Sample; 256], | ||||||
|  |     reg_zs: Vec<Channel>, | ||||||
|  |  | ||||||
|  |     loop_as: [LoopRange; 256], | ||||||
|  |     loop_ls: [LoopRange; 256], | ||||||
|  |     loop_zs: [LoopRange; 256], | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | struct LoopRange { | ||||||
|  |     start: i32, | ||||||
|  |     length: i32,  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Default for LoopRange { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { start: 0, length: 0 } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Scope { | ||||||
|  |     fn new(analysis: Analysis, inputs: Vec<Channel>) -> Scope { | ||||||
|  |         assert_eq!(inputs.len(), analysis.proto_n_inputs); // analysis is done elsewhere | ||||||
|  |  | ||||||
|  |         let reg_as: Vec<Channel> = inputs; | ||||||
|  |         let mut reg_ls: Vec<Channel> = vec![]; | ||||||
|  |         let reg_rs: [Sample; 256] = [Sample::default(); 256]; | ||||||
|  |         let mut reg_zs: Vec<Channel> = vec![]; | ||||||
|  |  | ||||||
|  |         for _ in 0..analysis.n_reg_ls { reg_ls.push(Channel::blank()); } | ||||||
|  |         for _ in 0..analysis.proto_n_outputs { reg_zs.push(Channel::blank()); } | ||||||
|  |  | ||||||
|  |         Scope { | ||||||
|  |             ix: Sample(0), | ||||||
|  |  | ||||||
|  |             reg_as,  | ||||||
|  |             reg_ls,  | ||||||
|  |             reg_rs,  | ||||||
|  |             reg_zs, | ||||||
|  |  | ||||||
|  |             loop_as: [LoopRange::default(); 256], | ||||||
|  |             loop_ls: [LoopRange::default(); 256], | ||||||
|  |             loop_zs: [LoopRange::default(); 256], | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn exit(self) -> Vec<Channel> { | ||||||
|  |         self.reg_zs | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn read(&self, src: MovSrc) -> Sample { | ||||||
|  |         let flat_ix = self.ix.0 as i32; | ||||||
|  |         let (chan, loop_) = match src { | ||||||
|  |             MovSrc::A(RegA(a)) => { | ||||||
|  |                 let a = a as usize; | ||||||
|  |                 (&self.reg_as[a], &self.loop_as[a]) | ||||||
|  |             } | ||||||
|  |             MovSrc::L(RegL(l)) => { | ||||||
|  |                 let l = l as usize; | ||||||
|  |                 (&self.reg_ls[l], &self.loop_ls[l]) | ||||||
|  |             } | ||||||
|  |             MovSrc::R(RegR(r)) => { | ||||||
|  |                 let r = r as usize; | ||||||
|  |                 return self.reg_rs[r] | ||||||
|  |             } | ||||||
|  |             MovSrc::Z(RegZ(z)) => { | ||||||
|  |                 let z = z as usize; | ||||||
|  |                 (&self.reg_zs[z], &self.loop_zs[z]) | ||||||
|  |             } | ||||||
|  |             MovSrc::Ix => return self.ix, | ||||||
|  |             MovSrc::Imm(i) => return i, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         let mut ix = flat_ix; | ||||||
|  |         if loop_.length != 0 { ix %= loop_.length; } | ||||||
|  |         ix += chan.formal().start + loop_.start; | ||||||
|  |  | ||||||
|  |         chan[ix] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn write(&mut self, ty: &MovType, dst: MovDst, value: Sample, offset: i32) { | ||||||
|  |         let flat_ix = self.ix.0 as i32; | ||||||
|  |         let (chan, loop_) = match dst { | ||||||
|  |             MovDst::L(RegL(l)) => { | ||||||
|  |                 let l = l as usize; | ||||||
|  |                 (&mut self.reg_ls[l], &self.loop_ls[l]) | ||||||
|  |             } | ||||||
|  |             MovDst::R(RegR(r)) => { | ||||||
|  |                 let r = r as usize; | ||||||
|  |                 self.reg_rs[r] = value; return | ||||||
|  |             } | ||||||
|  |             MovDst::Z(RegZ(z)) => { | ||||||
|  |                 let z = z as usize; | ||||||
|  |                 (&mut self.reg_zs[z], &self.loop_zs[z]) | ||||||
|  |             } | ||||||
|  |             MovDst::Ix => { self.ix = value; return } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         let mut ix = flat_ix + offset; | ||||||
|  |         if loop_.length != 0 { ix %= loop_.length; } | ||||||
|  |         ix += chan.formal().start + loop_.start; | ||||||
|  |  | ||||||
|  |         chan[ix] = value | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn replace(&mut self, dst: OupDst, value: Channel) { | ||||||
|  |         match dst { | ||||||
|  |             OupDst::L(RegL(l)) => {  | ||||||
|  |                 self.reg_ls[l as usize] = value;  | ||||||
|  |                 self.loop_ls[l as usize] = LoopRange::default()  | ||||||
|  |             } | ||||||
|  |             OupDst::Z(RegZ(z)) => {  | ||||||
|  |                 self.reg_zs[z as usize] = value;  | ||||||
|  |                 self.loop_zs[z as usize] = LoopRange::default()  | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn apply_range(&mut self, dst: RangeDst, start: i32, length: i32) { | ||||||
|  |         let lr = LoopRange{start, length}; | ||||||
|  |         match dst { | ||||||
|  |             RangeDst::A(RegA(a)) => { | ||||||
|  |                 self.loop_as[a as usize] = lr | ||||||
|  |             } | ||||||
|  |             RangeDst::L(RegL(l)) => { | ||||||
|  |                 self.loop_ls[l as usize] = lr | ||||||
|  |             } | ||||||
|  |             RangeDst::Z(RegZ(z)) => { | ||||||
|  |                 self.loop_zs[z as usize] = lr | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn inp(&self, src: InpSrc) -> Channel { | ||||||
|  |         let (chan, loop_) = match src { | ||||||
|  |             InpSrc::A(RegA(a)) => { | ||||||
|  |                 let a = a as usize; | ||||||
|  |                 (&self.reg_as[a], &self.loop_as[a]) | ||||||
|  |             } | ||||||
|  |             InpSrc::L(RegL(l)) => { | ||||||
|  |                 let l = l as usize; | ||||||
|  |                 (&self.reg_ls[l], &self.loop_ls[l]) | ||||||
|  |             } | ||||||
|  |             InpSrc::Z(RegZ(z)) => { | ||||||
|  |                 let z = z as usize; | ||||||
|  |                 (&self.reg_zs[z], &self.loop_zs[z]) | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         chan.slice(loop_.start, loop_.length) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Procedure { | ||||||
|  |     pub(crate) fn new(proto: Proto, blocks: Vec<Block>) -> Procedure { | ||||||
|  |         Procedure { | ||||||
|  |             proto, | ||||||
|  |             blocks, | ||||||
|  |             analysis: Cell::new(None), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn invoke(&self, rack: &Rack, inputs: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||||
|  |         let analysis = self.internal_analyze(rack)?; | ||||||
|  |  | ||||||
|  |         let mut scope = Scope::new(analysis, inputs); | ||||||
|  |         let mut stack: Vec<Channel> = vec![]; | ||||||
|  |  | ||||||
|  |         let block_ix = 0; | ||||||
|  |         loop { | ||||||
|  |             let block = &self.blocks[block_ix]; | ||||||
|  |             for instruction in block.body.iter() { | ||||||
|  |                 match instruction { | ||||||
|  |                     BodyInstruction::Mov(ty, dst, src) => { | ||||||
|  |                         scope.write(ty, *dst, scope.read(*src), 0) | ||||||
|  |                     } | ||||||
|  |                     BodyInstruction::Range(dst, start, len) => { | ||||||
|  |                         scope.apply_range(*dst, *start, *len) | ||||||
|  |                     } | ||||||
|  |                     BodyInstruction::Inp(src) => { | ||||||
|  |                         let slice = scope.inp(*src); | ||||||
|  |                         stack.push(slice) | ||||||
|  |                     } | ||||||
|  |                     BodyInstruction::Dup() => { | ||||||
|  |                         let top = stack.pop().expect("stack can't be empty"); | ||||||
|  |                         stack.push(top.clone()) | ||||||
|  |                     } | ||||||
|  |                     BodyInstruction::Oup(ty, dst) => { | ||||||
|  |                         let top = stack.pop().expect("stack can't be empty"); | ||||||
|  |                         let bounds = top.bounds(); | ||||||
|  |                         for ix in bounds.informal { | ||||||
|  |                             let offset = ix - bounds.formal.start; | ||||||
|  |                             scope.write(ty, dst.to_mov_dst(), top[ix], offset) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     BodyInstruction::Pop(dst) => { | ||||||
|  |                         let top = stack.pop().expect("stack can't be empty"); | ||||||
|  |                         scope.replace(*dst, top) | ||||||
|  |                     } | ||||||
|  |                     BodyInstruction::New(formal_start, formal_len, fill) => { | ||||||
|  |                         let start = scope.read(*formal_start); | ||||||
|  |                         let len = scope.read(*formal_len); | ||||||
|  |                         let fill = scope.read(*fill); | ||||||
|  |                         let mut chan = Channel::new((start.0 as i32)..((start.0+len.0) as i32)); | ||||||
|  |                         for i in chan.formal() { | ||||||
|  |                             chan[i] = fill | ||||||
|  |                         } | ||||||
|  |                         stack.push(chan) | ||||||
|  |                     } | ||||||
|  |                     BodyInstruction::Invoke(device, n_inputs, n_outputs) => { | ||||||
|  |                         let mut inputs: Vec<Channel> = vec![];  | ||||||
|  |                         std::mem::swap(&mut stack, &mut inputs); | ||||||
|  |                         assert_eq!(inputs.len(), *n_inputs); | ||||||
|  |                         let outputs = rack.invoke_positional(device, inputs)?; | ||||||
|  |                         assert_eq!(outputs.len(), *n_outputs); | ||||||
|  |                         stack.extend(outputs); | ||||||
|  |                         stack.reverse() | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             match block.exit { | ||||||
|  |                 ExitInstruction::Exit => return Ok(scope.exit()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn analyze(&self, rack: &Rack) -> Lansky<()> { | ||||||
|  |         self.internal_analyze(rack)?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn internal_analyze(&self, rack: &Rack) -> Lansky<Analysis> { | ||||||
|  |         if let Some(x) = self.analysis.get() { return Ok(x) } | ||||||
|  |  | ||||||
|  |         let mut analysis = Analysis {  | ||||||
|  |             stack_depth: 0,  | ||||||
|  |  | ||||||
|  |             proto_n_inputs: self.proto.inputs.len(), | ||||||
|  |             proto_n_outputs: self.proto.outputs.len(), | ||||||
|  |  | ||||||
|  |             n_reg_ls: 0  | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         if self.blocks.len() == 0 { | ||||||
|  |             return Err(LanskyError::NoFirstBlock); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for block in self.blocks.iter() { | ||||||
|  |             block.touch_analysis( | ||||||
|  |                 &mut analysis, &|d| rack.get_proto(d).cloned() | ||||||
|  |             )?; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.analysis.set(Some(analysis)); | ||||||
|  |  | ||||||
|  |         return Ok(analysis) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | impl Block { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis, get_proto: &impl Fn(&str) -> Lansky<Proto>) -> Lansky<()> { | ||||||
|  |         for instruction in self.body.iter() { | ||||||
|  |             instruction.touch_analysis(analysis, get_proto)?; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if analysis.stack_depth != 0 { | ||||||
|  |             return Err(LanskyError::WrongNStack {  | ||||||
|  |                 why: LanskyStackReason::BlockMustEndWithEmptyStack, | ||||||
|  |                 got: analysis.stack_depth, expected: 0  | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         self.exit.touch_analysis(analysis)?; | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl BodyInstruction { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis, get_proto: &impl Fn(&str) -> Lansky<Proto>) -> Lansky<()> { | ||||||
|  |         match self { | ||||||
|  |             BodyInstruction::Mov(_, dst, src) => { | ||||||
|  |                 dst.touch_analysis(analysis)?; | ||||||
|  |                 src.touch_analysis(analysis)?; | ||||||
|  |             } | ||||||
|  |             BodyInstruction::Range(dst, _, _) => { | ||||||
|  |                 dst.touch_analysis(analysis)?; | ||||||
|  |             } | ||||||
|  |             BodyInstruction::Inp(src) => { | ||||||
|  |                 analysis.stack_depth += 1; | ||||||
|  |                 src.touch_analysis(analysis)?; | ||||||
|  |             } | ||||||
|  |             BodyInstruction::Dup() => { | ||||||
|  |                 if analysis.stack_depth == 0 { | ||||||
|  |                     return Err(LanskyError::WrongNStack {  | ||||||
|  |                         why: LanskyStackReason::CannotDupEmptyStack, | ||||||
|  |                         expected: 1, got: 0  | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 analysis.stack_depth += 1; | ||||||
|  |             } | ||||||
|  |             BodyInstruction::Oup(_, dst) => { | ||||||
|  |                 if analysis.stack_depth == 0 { | ||||||
|  |                     return Err(LanskyError::WrongNStack {  | ||||||
|  |                         why: LanskyStackReason::CannotOupEmptyStack, | ||||||
|  |                         expected: 1, got: 0  | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 analysis.stack_depth -= 1; | ||||||
|  |  | ||||||
|  |                 dst.touch_analysis(analysis)?; | ||||||
|  |             } | ||||||
|  |             BodyInstruction::Pop(dst) => { | ||||||
|  |                 if analysis.stack_depth == 0 { | ||||||
|  |                     return Err(LanskyError::WrongNStack {  | ||||||
|  |                         why: LanskyStackReason::CannotPopEmptyStack, | ||||||
|  |                         expected: 1, got: 0  | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 analysis.stack_depth -= 1; | ||||||
|  |  | ||||||
|  |                 dst.touch_analysis(analysis)?; | ||||||
|  |             } | ||||||
|  |             BodyInstruction::New(formal_start, formal_len, fill) => { | ||||||
|  |                 analysis.stack_depth += 1; | ||||||
|  |  | ||||||
|  |                 formal_start.touch_analysis(analysis)?; | ||||||
|  |                 formal_len.touch_analysis(analysis)?; | ||||||
|  |                 fill.touch_analysis(analysis)?; | ||||||
|  |             } | ||||||
|  |             BodyInstruction::Invoke(device, n_inputs, n_outputs) => { | ||||||
|  |                 let proto = get_proto(device)?; | ||||||
|  |  | ||||||
|  |                 let actual_n_inputs = proto.inputs.len(); | ||||||
|  |                 let actual_n_outputs = proto.outputs.len(); | ||||||
|  |  | ||||||
|  |                 if *n_inputs != actual_n_inputs { | ||||||
|  |                     return Err(LanskyError::WrongNInputs { expected: actual_n_inputs, got: *n_inputs }) | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if *n_outputs != actual_n_outputs { | ||||||
|  |                     return Err(LanskyError::WrongNInputs { expected: actual_n_inputs, got: *n_outputs }) | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if analysis.stack_depth != *n_inputs { | ||||||
|  |                     return Err(LanskyError::WrongNStack { | ||||||
|  |                         why: LanskyStackReason::InvokeNInputs, | ||||||
|  |                         expected: *n_inputs, | ||||||
|  |                         got: analysis.stack_depth, | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 analysis.stack_depth = *n_outputs; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl ExitInstruction { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         match self { | ||||||
|  |             ExitInstruction::Exit => {} | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl MovDst { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         match self { | ||||||
|  |             Self::L(l) => l.touch_analysis(analysis), | ||||||
|  |             Self::R(r) => r.touch_analysis(analysis), | ||||||
|  |             Self::Z(z) => z.touch_analysis(analysis), | ||||||
|  |             Self::Ix => Ok(()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl MovSrc { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         match self { | ||||||
|  |             Self::A(a) => a.touch_analysis(analysis), | ||||||
|  |             Self::L(l) => l.touch_analysis(analysis), | ||||||
|  |             Self::R(r) => r.touch_analysis(analysis), | ||||||
|  |             Self::Z(z) => z.touch_analysis(analysis), | ||||||
|  |             Self::Ix => Ok(()), | ||||||
|  |             Self::Imm(_) => Ok(()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl RangeDst { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         match self { | ||||||
|  |             Self::A(a) => a.touch_analysis(analysis), | ||||||
|  |             Self::L(l) => l.touch_analysis(analysis), | ||||||
|  |             Self::Z(z) => z.touch_analysis(analysis), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl InpSrc { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         match self { | ||||||
|  |             Self::A(a) => a.touch_analysis(analysis), | ||||||
|  |             Self::L(l) => l.touch_analysis(analysis), | ||||||
|  |             Self::Z(z) => z.touch_analysis(analysis), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl OupDst { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         match self { | ||||||
|  |             Self::L(l) => l.touch_analysis(analysis), | ||||||
|  |             Self::Z(z) => z.touch_analysis(analysis), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | impl RegA { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         if self.0 as usize >= analysis.proto_n_inputs { | ||||||
|  |             return Err(LanskyError::MissingRegA { i: self.0 as usize }); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl RegL { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         analysis.n_reg_ls = analysis.n_reg_ls.max(self.0 as usize); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl RegR { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl RegZ { | ||||||
|  |     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||||
|  |         if self.0 as usize >= analysis.proto_n_outputs { | ||||||
|  |             return Err(LanskyError::MissingRegZ { i: self.0 as usize }); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user