Include my code

This commit is contained in:
2023-06-23 20:55:40 -07:00
parent b81bf4cb13
commit acd0fd1b25
11 changed files with 1111 additions and 0 deletions

131
src/audio.rs Normal file
View 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
View 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
View 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
View 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
View 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())
}
}

View 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
View 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(())
}
}