Include my code

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

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
example.wav
.vscode/

25
Cargo.lock generated Normal file
View 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
View 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
View 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
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(())
}
}