mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: try to get a simplified parser going
This commit is contained in:
parent
fc82d6ff9b
commit
600d0b3aca
17 changed files with 676 additions and 133 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -1369,7 +1369,6 @@ dependencies = [
|
|||
"atomic_float",
|
||||
"backtrace",
|
||||
"clap",
|
||||
"clojure-reader",
|
||||
"jack",
|
||||
"livi",
|
||||
"midly",
|
||||
|
|
@ -1378,18 +1377,25 @@ dependencies = [
|
|||
"quanta",
|
||||
"rand",
|
||||
"symphonia",
|
||||
"tek_edn",
|
||||
"tek_layout",
|
||||
"toml",
|
||||
"uuid",
|
||||
"wavers",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tek_edn"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clojure-reader",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tek_engine"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"better-panic",
|
||||
"clojure-reader",
|
||||
"crossterm",
|
||||
"ratatui",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ version = "0.2.0"
|
|||
|
||||
[dependencies]
|
||||
tek_layout = { path = "./layout" }
|
||||
tek_edn = { optional = true, path = "./edn" }
|
||||
|
||||
atomic_float = "1.0.0"
|
||||
backtrace = "0.3.72"
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
clojure-reader = "0.3.0"
|
||||
jack = { path = "./rust-jack" }
|
||||
livi = "0.7.4"
|
||||
midly = "0.5"
|
||||
|
|
@ -27,6 +27,10 @@ wavers = "1.4.3"
|
|||
#vst3 = "0.1.0"
|
||||
#winit = { version = "0.30.4", features = [ "x11" ] }
|
||||
|
||||
[features]
|
||||
default = ["edn"]
|
||||
edn = ["tek_edn"]
|
||||
|
||||
[[bin]]
|
||||
name = "tek_arranger"
|
||||
path = "bin/cli_arranger.rs"
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@ from there, use the commands in the `Justfile`, e.g.:
|
|||
just arranger
|
||||
```
|
||||
|
||||
note that `tek > 0.2.0-rc.7` will require rust nightly
|
||||
for the unstable features `type_alias_impl_trait` and
|
||||
`impl_trait_in_assoc_type`. make some noise for lucky
|
||||
[**rust rfc2515**](https://github.com/rust-lang/rust/issues/63063)
|
||||
if you want to see this buildable with stable/beta.
|
||||
|
||||
## design goals
|
||||
|
||||
### lightweight
|
||||
|
|
|
|||
92
edn/Cargo.lock
generated
Normal file
92
edn/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "clojure-reader"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edf141eea627c101a97509266bc9f6ba8cd408618f5e2ac4a0cb6b64b1d4ea8"
|
||||
dependencies = [
|
||||
"ordered-float",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_panic"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17"
|
||||
|
||||
[[package]]
|
||||
name = "konst"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9"
|
||||
dependencies = [
|
||||
"const_panic",
|
||||
"konst_kernel",
|
||||
"konst_proc_macros",
|
||||
"typewit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "konst_kernel"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c"
|
||||
dependencies = [
|
||||
"typewit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "konst_proc_macros"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tek_edn"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clojure-reader",
|
||||
"konst",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typewit"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0"
|
||||
dependencies = [
|
||||
"typewit_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typewit_proc_macros"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6"
|
||||
8
edn/Cargo.toml
Normal file
8
edn/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "tek_edn"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
clojure-reader = "0.3.0"
|
||||
konst = "0.3.16"
|
||||
118
edn/src/edn_layout.rs
Normal file
118
edn/src/edn_layout.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use crate::*;
|
||||
|
||||
#[cfg(test)] #[test] fn test_edn_layout () -> Result<(), ParseError> {
|
||||
let source = include_str!("example.edn");
|
||||
let layout = Item::read_all(source)?;
|
||||
panic!("{layout:?}");
|
||||
let content = EdnLayout::from(&layout);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [Item]> for EdnLayout<'a> {
|
||||
fn from (items: &'a [Item]) -> Self {
|
||||
Self(items)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EdnLayout<'a, E>(&'a [Item]);
|
||||
impl<'a, E> EdnLayout<'a, E> {
|
||||
fn get_content (&self) -> impl Content<E> {
|
||||
}
|
||||
}
|
||||
|
||||
//pub struct EdnContent<'a, T>(T, &'a [Item]);
|
||||
|
||||
#[macro_export] macro_rules! edn_ns {
|
||||
($name:literal = $Host:ident<$E:ident: $Engine:path> |$args:ident| { $(
|
||||
($fn:literal
|
||||
$Struct:ident
|
||||
$(<$($G:ident$(: $Generic:ty)?),+>)?
|
||||
$(::$Variant:ident)?
|
||||
($($arg:expr),*))
|
||||
)* }) => {
|
||||
//pub trait $Host<$E: Engine> {
|
||||
//fn read_one <'e> (edn: &[Edn<'e>]) -> impl Content<$E> {
|
||||
//if let Some(Edn::Symbol(name)) = edn.get(0) {
|
||||
//match *name {
|
||||
//$(
|
||||
//$fn => $Struct$(::$Variant)?($($arg),+),
|
||||
//)*
|
||||
//_ => {}
|
||||
//}
|
||||
//} else {
|
||||
//panic!("invalid edn")
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
};
|
||||
}
|
||||
|
||||
edn_ns! {
|
||||
|
||||
"when" = When<A>(args[0].into(), args[1].into()),
|
||||
|
||||
"either" = Either<A, B>(args[0].into(), args[1].into(), args[2].into()),
|
||||
|
||||
"map" = Map<A, B, I, F, G>(args[0].into(), args[1].into()),
|
||||
|
||||
"fill" = edn_ns! {
|
||||
"x" = Fixed<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Fixed<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Fixed<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"fixed" = edn_ns! {
|
||||
"x" = Fixed<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Fixed<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Fixed<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"shrink" = edn_ns! {
|
||||
"x" = Shrink<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Shrink<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Shrink<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"expand" = edn_ns! {
|
||||
"x" = Expand<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Expand<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Expand<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"min" = edn_ns! {
|
||||
"x" = Min<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Min<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Min<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"max" = edn_ns! {
|
||||
"x" = Max<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Max<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Max<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"push" = edn_ns! {
|
||||
"x" = Push<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Push<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Push<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"pull" = edn_ns! {
|
||||
"x" = Pull<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Pull<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Pull<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"margin" = edn_ns! {
|
||||
"x" = Margin<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Margin<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Margin<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
"padding" = edn_ns! {
|
||||
"x" = Padding<T>::X(args[0].into(), args[1].into()),
|
||||
"y" = Padding<T>::Y(args[0].into(), args[1].into()),
|
||||
"xy" = Padding<T>::XY(args[0].into(), args[1].into(), args[2].into()),
|
||||
},
|
||||
|
||||
}
|
||||
296
edn/src/edn_lib.rs
Normal file
296
edn/src/edn_lib.rs
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
use std::collections::BTreeMap;
|
||||
pub use clojure_reader::edn::Edn;
|
||||
|
||||
#[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> {
|
||||
use Item::*;
|
||||
assert_eq!(Item::read_all("")?,
|
||||
vec![]);
|
||||
assert_eq!(Item::read_all(" ")?,
|
||||
vec![]);
|
||||
assert_eq!(Item::read_all("1234")?,
|
||||
vec![Num(1234)]);
|
||||
assert_eq!(Item::read_all("1234 5 67")?,
|
||||
vec![Num(1234), Num(5), Num(67)]);
|
||||
assert_eq!(Item::read_all("foo/bar")?,
|
||||
vec![Key("foo/bar".into())]);
|
||||
assert_eq!(Item::read_all(":symbol")?,
|
||||
vec![Sym(":symbol".into())]);
|
||||
assert_eq!(Item::read_all(" foo/bar :baz 456")?,
|
||||
vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)]);
|
||||
assert_eq!(Item::read_all(" (foo/bar :baz 456) ")?,
|
||||
vec![Exp(vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)])]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn number (digits: &str) -> usize {
|
||||
let mut value = 0;
|
||||
for c in digits.chars() {
|
||||
value = 10 * value + digit(c);
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
const fn digit (c: char) -> usize {
|
||||
match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
|
||||
'5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, _ => unreachable!() }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
Empty,
|
||||
Unexpected(char),
|
||||
Incomplete
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub enum Item {
|
||||
#[default] Nil,
|
||||
Num(usize),
|
||||
Sym(String),
|
||||
Key(String),
|
||||
Exp(Vec<Item>),
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn read_all <'a> (mut source: &'a str) -> Result<Vec<Self>, ParseError> {
|
||||
let mut items = vec![];
|
||||
loop {
|
||||
if source.len() == 0 {
|
||||
break
|
||||
}
|
||||
let (remaining, token) = Token::chomp(source)?;
|
||||
match Item::read(token)? { Item::Nil => {}, item => items.push(item) };
|
||||
source = remaining
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
pub fn read <'a> (token: Token<'a>) -> Result<Self, ParseError> {
|
||||
use Token::*;
|
||||
Ok(match token {
|
||||
Nil => Item::Nil,
|
||||
Num(chars, index, length) =>
|
||||
Self::Num(number(&chars[index..index+length])),
|
||||
Sym(chars, index, length) =>
|
||||
Self::Sym(chars[index..index+length].to_string()),
|
||||
Key(chars, index, length) =>
|
||||
Self::Key(chars[index..index+length].to_string()),
|
||||
Exp(chars, index, length, 0) =>
|
||||
Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?),
|
||||
_ => panic!("unclosed delimiter")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq)]
|
||||
pub enum Token<'a> {
|
||||
#[default] Nil,
|
||||
Num(&'a str, usize, usize),
|
||||
Sym(&'a str, usize, usize),
|
||||
Key(&'a str, usize, usize),
|
||||
Exp(&'a str, usize, usize, usize),
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> {
|
||||
use Token::*;
|
||||
let mut state = Self::default();
|
||||
for (index, c) in source.char_indices() {
|
||||
state = match state {
|
||||
// must begin expression
|
||||
Nil => match c {
|
||||
' '|'\n'|'\r'|'\t' => Nil,
|
||||
'(' => Exp(source, index, 1, 1),
|
||||
':' => Sym(source, index, 1),
|
||||
'0'..='9' => Num(source, index, 1),
|
||||
'a'..='z' => Key(source, index, 1),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
},
|
||||
Num(_, _, 0) => unreachable!(),
|
||||
Sym(_, _, 0) => unreachable!(),
|
||||
Key(_, _, 0) => unreachable!(),
|
||||
Num(source, index, length) => match c {
|
||||
'0'..='9' => Num(source, index, length + 1),
|
||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
},
|
||||
Sym(source, index, length) => match c {
|
||||
'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1),
|
||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
},
|
||||
Key(source, index, length) => match c {
|
||||
'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1),
|
||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
},
|
||||
Exp(source, index, length, 0) => match c {
|
||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
},
|
||||
Exp(source, index, length, depth) => match c {
|
||||
')' => Exp(source, index, length + 1, depth - 1),
|
||||
'(' => Exp(source, index, length + 1, depth + 1),
|
||||
_ => Exp(source, index, length + 1, depth)
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(("", state))
|
||||
}
|
||||
}
|
||||
|
||||
//#[derive(Debug, Copy, Clone, Default, PartialEq)]
|
||||
//pub struct Items<'a>(&'a [Item<'a>]);
|
||||
//impl<'a> Items<'a> {
|
||||
//fn iter (&'a self) -> ItemsIterator<'a> {
|
||||
//ItemsIterator(0, self.0)
|
||||
//}
|
||||
//}
|
||||
|
||||
//pub struct ItemsIterator<'a>(usize, &'a [Item<'a>]);
|
||||
//impl<'a> Iterator for ItemsIterator<'a> {
|
||||
//type Item = &'a Item<'a>;
|
||||
//fn next (&mut self) -> Option<Self::Item> {
|
||||
//let item = self.1.get(self.0);
|
||||
//self.0 += 1;
|
||||
//item
|
||||
//}
|
||||
//}
|
||||
|
||||
/*
|
||||
|
||||
nice but doesn't work without compile time slice concat
|
||||
(which i guess could be implemeted using an unsafe linked list?)
|
||||
never done that one before im ny life, might try
|
||||
|
||||
use konst::slice_concat;
|
||||
|
||||
const fn read <'a> (
|
||||
chars: impl Iterator<Item = char>
|
||||
) -> Result<Range<'a>, ParseError> {
|
||||
use Range::*;
|
||||
let mut state = Range::Nil;
|
||||
let mut tokens: &[Range<'a>] = &[];
|
||||
while let Some(c) = chars.next() {
|
||||
state = match state {
|
||||
// must begin expression
|
||||
Nil => match c {
|
||||
' ' => Nil,
|
||||
'(' => Exp(&[]),
|
||||
':' => Sym(&[]),
|
||||
'1'..'9' => Num(digit(c)),
|
||||
'a'..'z' => Key(&[&[c]]),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
},
|
||||
Num(b) => match c {
|
||||
' ' => return Ok(Num(digit(c))),
|
||||
'1'..'9' => Num(b*10+digit(c)),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
}
|
||||
Sym([]) => match c {
|
||||
'a'..'z' => Sym(&[c]),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
},
|
||||
Sym([b @ ..]) => match c {
|
||||
' ' => return Ok(Sym(&b)),
|
||||
'a'..'z' | '0'..'9' | '-' => Sym(&[..b, c]),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
}
|
||||
Key([[b @ ..]]) => match c {
|
||||
' ' => return Ok(Key(&[&b])),
|
||||
'/' => Key(&[&b, &[]]),
|
||||
'a'..'z' | '0'..'9' | '-' => Key(&[&[..b, c], &[]]),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
}
|
||||
Key([s @ .., []]) => match c {
|
||||
'a'..'z' => Key(&[..s, &[c]]),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
}
|
||||
Key([s @ .., [b @ ..]]) => match c {
|
||||
'/' => Key([..s, &b, &[]]),
|
||||
'a'..'z' | '0'..'9' | '-' => Key(&[..s, &[..b, c]]),
|
||||
_ => return Err(ParseError::Unexpected(c))
|
||||
}
|
||||
// expression must begin with key or symbol
|
||||
Exp([]) => match c {
|
||||
' ' => Exp(&[]),
|
||||
')' => return Err(ParseError::Empty),
|
||||
':' => Exp(&[Sym(&[':'])]),
|
||||
c => Exp(&[Key(&[&[c]])]),
|
||||
},
|
||||
|
||||
// expression can't begin with number
|
||||
Exp([Num(num)]) => return Err(ParseError::Unexpected(c)),
|
||||
|
||||
// symbol begins with : and lowercase a-z
|
||||
Exp([Sym([':'])]) => match c {
|
||||
'a'..'z' => Exp(&[Sym(&[':', c])]),
|
||||
_ => return Err(ParseError::Unexpected(c)),
|
||||
},
|
||||
|
||||
// any other char is part of symbol until space or )
|
||||
Exp([Sym([':', b @ ..])]) => match c {
|
||||
')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil },
|
||||
' ' => Exp(&[Sym(&[':', ..b]), Nil]),
|
||||
c => Exp(&[Sym(&[':', ..b, c])]),
|
||||
},
|
||||
|
||||
// key begins with lowercase a-z
|
||||
Exp([Key([])]) => match c {
|
||||
'a'..'z' => Exp([Key([[c]])]),
|
||||
_ => return Err(ParseError::Unexpected(c)),
|
||||
},
|
||||
|
||||
// any other char is part of key until slash space or )
|
||||
Exp([Key([[b @ ..]])]) => match c {
|
||||
'/' => Exp(&[Key(&[[..b], []])]),
|
||||
' ' => Exp(&[Key(&[[..b]]), Nil]),
|
||||
')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil },
|
||||
c => Exp(&[Key(&[[..b, c]])])
|
||||
}
|
||||
|
||||
// slash adds new section to key
|
||||
Exp([Key([b @ .., []])]) => match c {
|
||||
'/' => Exp(&[Key(&[[..b], []])]),
|
||||
' ' => Exp(&[Key(&[[..b]]), Nil]),
|
||||
')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil },
|
||||
c => Exp(&[Key(&[[..b, c]])])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/// EDN parsing helper.
|
||||
#[macro_export] macro_rules! edn {
|
||||
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
match $edn { $($pat => $expr),* }
|
||||
};
|
||||
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
for $edn in $args {
|
||||
edn!($edn { $($pat => $expr),* })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait FromEdn<C>: Sized {
|
||||
const ID: &'static str;
|
||||
fn from_edn (context: C, expr: &[Edn<'_>]) ->
|
||||
std::result::Result<Self, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
/// Implements the [FromEdn] trait.
|
||||
#[macro_export] macro_rules! from_edn {
|
||||
($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => {
|
||||
impl FromEdn<$Context> for $T {
|
||||
const ID: &'static str = $id;
|
||||
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
|
||||
$body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
edn/src/example.edn
Normal file
12
edn/src/example.edn
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
(sized
|
||||
(bsp/s (fill/x (fixed/y 2 (lay
|
||||
(align/w :input-meter-l)
|
||||
(align/e :input-meter-r)
|
||||
(align/x :transport))))
|
||||
(bsp/n (row :clip-play :clip-next :clip-edit :edit-stat)
|
||||
(bsp/n (max/y :sample-h (fill/xy :sample-view))
|
||||
(bsp/n (align/w (fixed/y 1 :sample-stat))
|
||||
(bsp/n (fixed/x :pool-w :pool-view)
|
||||
(fill/xy (bsp/e
|
||||
(fixed/x :samples-w (push/y :samples-y :samples-view))
|
||||
:midi-view))))))))
|
||||
2
edn/src/lib.rs
Normal file
2
edn/src/lib.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mod edn_lib; pub use self::edn_lib::*;
|
||||
mod edn_layout; pub use self::edn_layout::*;
|
||||
|
|
@ -7,4 +7,3 @@ version = "0.2.0"
|
|||
crossterm = "0.28.1"
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||
better-panic = "0.3.0"
|
||||
clojure-reader = "0.3.0"
|
||||
|
|
|
|||
|
|
@ -1,36 +1 @@
|
|||
use crate::*;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub use clojure_reader::edn::Edn;
|
||||
|
||||
/// EDN parsing helper.
|
||||
#[macro_export] macro_rules! edn {
|
||||
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
match $edn { $($pat => $expr),* }
|
||||
};
|
||||
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
for $edn in $args {
|
||||
edn!($edn { $($pat => $expr),* })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait FromEdn<C>: Sized {
|
||||
const ID: &'static str;
|
||||
fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually<Self>;
|
||||
}
|
||||
|
||||
/// Implements the [FromEdn] trait.
|
||||
#[macro_export] macro_rules! from_edn {
|
||||
($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => {
|
||||
impl FromEdn<$Context> for $T {
|
||||
const ID: &'static str = $id;
|
||||
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
|
||||
$body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
|
||||
//mod component; pub use self::component::*;
|
||||
mod engine; pub use self::engine::*;
|
||||
mod input; pub use self::input::*;
|
||||
mod output; pub use self::output::*;
|
||||
|
||||
pub mod tui;
|
||||
pub mod edn;
|
||||
#[cfg(feature = "edn")] pub mod edn;
|
||||
|
||||
pub use std::error::Error;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
use crate::*;
|
||||
use ::tek_engine::edn::*;
|
||||
|
||||
macro_rules! edn_module {
|
||||
($name:literal = $Host:ident<$E:ident: $Engine:path> { $(
|
||||
(defn $fn:ident
|
||||
$(<$($G:ident: $Generic:ty),+>)?
|
||||
$Struct:ident($($arg:ident : $Arg:ty),*))
|
||||
)* }) => {
|
||||
pub trait $Host<$E: Engine> {
|
||||
pub fn read_one <'e> (edn: &[Edn<'e>]) -> impl Content<$E> {
|
||||
if let Some(Edn::Symbol(name)) = edn.get(0) {
|
||||
match name {
|
||||
$(
|
||||
stringify!($fn) =>
|
||||
),*
|
||||
}
|
||||
} else {
|
||||
panic!("invalid edn")
|
||||
}
|
||||
}
|
||||
$(
|
||||
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//edn_module! {
|
||||
//(host LayoutEdn<E>)
|
||||
//(name "layout")
|
||||
|
||||
//(defn when <A: Content<E>>
|
||||
//When(cond: bool, item: A))
|
||||
|
||||
//(defn either <A: Content<E>, B: Content<E>>
|
||||
//Either(cond: bool, a: A, b: B))
|
||||
|
||||
//(defn map <
|
||||
//A: Content<E>,
|
||||
//B: Content<E>,
|
||||
//I: Iterator<Item = A> + Send + Sync,
|
||||
//F: Fn() -> I + Send + Sync,
|
||||
//G: Fn(A, usize)->B + Send + Sync
|
||||
//>
|
||||
//Map(get_iterator: I, callback: G))
|
||||
//}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
//mod collection; pub use self::collection::*;
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
|
||||
mod align; pub use self::align::*;
|
||||
mod direction; pub use self::direction::*;
|
||||
mod layout_edn; pub use self::edn::*;
|
||||
mod measure; pub use self::measure::*;
|
||||
mod ops; pub use self::ops::*;
|
||||
mod transform_xy; pub use self::transform_xy::*;
|
||||
|
|
|
|||
12
src/groovebox/groovebox.edn
Normal file
12
src/groovebox/groovebox.edn
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
(sized
|
||||
(bsp/s (fill/x (fixed/y 2 (lay
|
||||
(align/w :input-meter-l)
|
||||
(align/e :input-meter-r)
|
||||
(align/x :transport))))
|
||||
(bsp/n (row :clip-play :clip-next :clip-edit :edit-stat)
|
||||
(bsp/n (max/y :sample-h (fill/xy :sample-view))
|
||||
(bsp/n (align/w (fixed/y 1 :sample-stat))
|
||||
(bsp/n (fixed/x :pool-w :pool-view)
|
||||
(fill/xy (bsp/e
|
||||
(fixed/x :samples-w (push/y :samples-y :samples-view))
|
||||
:midi-view))))))))
|
||||
|
|
@ -1,49 +1,111 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
use std::marker::ConstParamTy;
|
||||
|
||||
render!(Tui: (self: Groovebox) => self.size.of(
|
||||
Bsp::s(self.toolbar_view(),
|
||||
Bsp::n(self.selector_view(),
|
||||
Bsp::n(self.sample_view(),
|
||||
Bsp::n(self.status_view(),
|
||||
Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor)))))))));
|
||||
const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn");
|
||||
|
||||
impl Groovebox {
|
||||
fn toolbar_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
Fill::x(Fixed::y(2, lay!(
|
||||
Align::w(Meter("L/", self.sampler.input_meter[0])),
|
||||
Align::e(Meter("R/", self.sampler.input_meter[1])),
|
||||
Align::x(TransportView::new(true, &self.player.clock)),
|
||||
)))
|
||||
}
|
||||
fn selector_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
row!(
|
||||
ClipSelected::play_phrase(&self.player),
|
||||
ClipSelected::next_phrase(&self.player),
|
||||
MidiEditClip(&self.editor),
|
||||
MidiEditStatus(&self.editor),
|
||||
)
|
||||
}
|
||||
fn sample_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
let note_pt = self.editor.note_point();
|
||||
let sample_h = if self.compact { 0 } else { 5 };
|
||||
Max::y(sample_h, Fill::xy(
|
||||
SampleViewer::from_sampler(&self.sampler, note_pt)))
|
||||
}
|
||||
fn status_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
let note_pt = self.editor.note_point();
|
||||
Align::w(Fixed::y(1, SamplerStatus(&self.sampler, note_pt)))
|
||||
}
|
||||
fn pool_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
let w = self.size.w();
|
||||
let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
Fixed::x(if self.compact { 5 } else { pool_w },
|
||||
PoolView(self.compact, &self.pool))
|
||||
}
|
||||
fn sampler_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
let sampler_w = if self.compact { 4 } else { 11 };
|
||||
let sampler_y = if self.compact { 1 } else { 0 };
|
||||
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
|
||||
SampleList::new(self.compact, &self.sampler, &self.editor))))
|
||||
impl Content<Tui> for Groovebox {
|
||||
fn content (&self) -> impl Content<Tui> {
|
||||
EdnView::parse(self.edn.as_slice())
|
||||
}
|
||||
}
|
||||
//render!(Tui: (self: Groovebox) => self.size.of(
|
||||
//Bsp::s(self.toolbar_view(),
|
||||
//Bsp::n(self.selector_view(),
|
||||
//Bsp::n(self.sample_view(),
|
||||
//Bsp::n(self.status_view(),
|
||||
//Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor)))))))));
|
||||
|
||||
macro_rules! edn_context {
|
||||
($Struct:ident |$l:lifetime, $state:ident| {
|
||||
$($key:literal = $field:ident: $Type:ty => $expr:expr,)*
|
||||
}) => {
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EdnView<$l> { $($field: Option<$Type>),* }
|
||||
|
||||
impl<$l> EdnView<$l> {
|
||||
pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> {
|
||||
let imports = Self::imports_all(edn);
|
||||
move |state| {
|
||||
let mut context = EdnView::default();
|
||||
for import in imports.iter() {
|
||||
context.import(state, import)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> {
|
||||
let mut imports = vec![];
|
||||
for edn in edn.iter() {
|
||||
for import in Self::imports_one(edn) {
|
||||
imports.push(import);
|
||||
}
|
||||
}
|
||||
imports
|
||||
}
|
||||
fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> {
|
||||
match edn {
|
||||
Edn::Symbol(import) => vec![import],
|
||||
Edn::List(edn) => Self::imports_all(edn.as_slice()),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
pub fn import (&mut self, $state: &$l$Struct, key: &str) {
|
||||
match key {
|
||||
$($key => self.$field = Some($expr),)*
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edn_context!(Groovebox |'a, state| {
|
||||
":input-meter-l" = input_meter_l: Meter<'a> => Meter("L/", state.sampler.input_meter[0]),
|
||||
":input-meter-r" = input_meter_r: Meter<'a> => Meter("R/", state.sampler.input_meter[1]),
|
||||
|
||||
":transport" = transport: TransportView<'a> => TransportView::new(true, &state.player.clock),
|
||||
|
||||
":clip-play" = clip_play: ClipSelected => ClipSelected::play_phrase(&state.player),
|
||||
":clip-next" = clip_next: ClipSelected => ClipSelected::next_phrase(&state.player),
|
||||
":clip-edit" = clip_edit: MidiEditClip<'a> => MidiEditClip(&state.editor),
|
||||
|
||||
":edit-stat" = edit_stat: MidiEditStatus<'a> => MidiEditStatus(&state.editor),
|
||||
|
||||
":sample-h" = sample_h: u16 => if state.compact { 0 } else { 5 },
|
||||
":sample-view" = sample_view: SampleViewer => SampleViewer::from_sampler(
|
||||
&state.sampler, state.editor.note_point()),
|
||||
":sample-stat" = sample_stat: SamplerStatus<'a> => SamplerStatus(
|
||||
&state.sampler, state.editor.note_point()),
|
||||
|
||||
":pool-w" = pool_w: u16 => if state.compact { 5 } else {
|
||||
let w = state.size.w();
|
||||
if w > 60 { 20 } else if w > 40 { 15 } else { 10 } },
|
||||
":pool-view" = pool_view: PoolView<'a> => PoolView(state.compact, &state.pool),
|
||||
|
||||
":samples-w" = samples_w: u16 => if state.compact { 4 } else { 11 },
|
||||
":samples-y" = samples_y: u16 => if state.compact { 1 } else { 0 },
|
||||
":samples-view" = samples_view: SampleList<'a> => SampleList::new(
|
||||
state.compact, &state.sampler, &state.editor),
|
||||
|
||||
":midi-view" = midi_view: &'a MidiEditor => &state.editor,
|
||||
});
|
||||
|
||||
//impl Groovebox {
|
||||
//fn status_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
//let note_pt = self.editor.note_point();
|
||||
//Align::w(Fixed::y(1, ))
|
||||
//}
|
||||
//fn pool_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
//let w = self.size.w();
|
||||
//let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
//Fixed::x(if self.compact { 5 } else { pool_w },
|
||||
//)
|
||||
//}
|
||||
//fn sampler_view (&self) -> impl Content<Tui> + use<'_> {
|
||||
//let sampler_w = if self.compact { 4 } else { 11 };
|
||||
//let sampler_y = if self.compact { 1 } else { 0 };
|
||||
//Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
|
||||
//SampleList::new(self.compact, &self.sampler, &self.editor))))
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#![allow(unused)]
|
||||
#![allow(clippy::unit_arg)]
|
||||
#![feature(adt_const_params)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
#![feature(associated_type_defaults)]
|
||||
|
||||
pub use ::tek_layout;
|
||||
pub use ::tek_layout::tek_engine;
|
||||
|
|
@ -12,7 +16,6 @@ pub(crate) use ::tek_layout::{
|
|||
Output, Content, Thunk, render,
|
||||
Input, Handle, handle,
|
||||
kexp, kpat,
|
||||
edn::*,
|
||||
tui::{
|
||||
Tui,
|
||||
TuiIn, key, ctrl, shift, alt,
|
||||
|
|
@ -33,6 +36,9 @@ pub(crate) use ::tek_layout::{
|
|||
}
|
||||
};
|
||||
|
||||
pub use ::tek_edn;
|
||||
pub(crate) use ::tek_edn::*;
|
||||
|
||||
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
||||
pub(crate) use std::collections::BTreeMap;
|
||||
pub(crate) use std::error::Error;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue