From 7271081fc99a6bbecf05bbd935e9cdea25a665e2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 14 Jul 2025 22:22:45 +0300 Subject: [PATCH 1/4] wip: mrr --- ] | 200 --------------------------- dsl/src/cst.rs | 115 ++++++++------- dsl/src/dsl.rs | 30 ++-- dsl/src/lib.rs | 2 - editor/Cargo.toml | 5 + vin/Cargo.toml => editor/src/main.rs | 0 input/src/input_dsl.rs | 46 ++++-- input/src/lib.rs | 3 +- tengri/src/test.rs | 8 +- tui/src/tui_engine/tui_input.rs | 6 +- 10 files changed, 119 insertions(+), 296 deletions(-) delete mode 100644 ] create mode 100644 editor/Cargo.toml rename vin/Cargo.toml => editor/src/main.rs (100%) diff --git a/] b/] deleted file mode 100644 index 93bdbbd..0000000 --- a/] +++ /dev/null @@ -1,200 +0,0 @@ -use crate::*; -/// A collection of input bind. -/// -/// Each contained layer defines a mapping from input event to command invocation -/// over a given state. Furthermore, each layer may have an associated cond, -/// so that only certain layers are active at a given time depending on state. -#[derive(Debug, Default)] pub struct InputMap(std::collections::BTreeMap); -impl InputMap { - /// Create input layer collection from path to text file. - pub fn from_path > (path: P) -> Usually { - if !exists(path.as_ref())? { - return Err(format!("(e5) not found: {path:?}").into()) - } - Self::from_source(read_and_leak(path)?) - } - /// Create input layer collection from string. - pub fn from_source > (source: S) -> Usually { - Self::from_dsl(CstIter::from(source.as_ref())) - } - /// Create input layer collection from DSL. - pub fn from_dsl (dsl: impl Dsl) -> Usually { - use DslVal::*; - let mut input_map: Self = Self(Default::default()); - while let Exp(_, mut exp) = dsl.val() { - match exp.nth(0).map(|x|x.val()) { - Some(Str(path)) => { - let path = PathBuf::from(path.as_ref()); - let module = InputMap::::from_path(&path)?; - for (key, val) in module.0.into_iter() { - } - }, - Some(Exp(_, expr)) if let Some(Sym(sym)) = expr.nth(0) => { - //input_map.unconditional.push(expr); - todo!("binding"); - }, - Some(Exp(_, expr)) if expr.nth(0).map(|x|x.val()) == Some(Key("if")) => { - todo!("conditional binding"); - }, - _ => return Result::Err("invalid token in keymap".into()), - } - } - Ok(input_map) - } - /// Evaluate the active layers for a given state, - /// returning the command to be executed, if any. - pub fn handle (&self, state: &mut S, input: I) -> Perhaps where - S: DslInto + DslInto, - O: Command - { - todo!(); - //let layers = self.0.as_slice(); - //for InputLayer { cond, bind } in layers.iter() { - //let mut matches = true; - //if let Some(cond) = cond { - //matches = state.dsl_into(cond, ||format!("input: no cond").into())?; - //} - //if matches - //&& let Some(exp) = bind.val().exp_head() - //&& input.dsl_into(exp, ||format!("InputMap: input.eval(binding) failed").into())? - //&& let Some(command) = state.try_dsl_into(exp)? { - //return Ok(Some(command)) - //} - //} - Ok(None) - } - /* - /// Create an input map with a single non-condal layer. - /// (Use [Default::default] to get an empty map.) - pub fn new (layer: DslVal) -> Self { - Self::default().layer(layer) - } - /// Add layer, return `Self`. - pub fn layer (mut self, layer: DslVal) -> Self { - self.add_layer(layer); self - } - /// Add condal layer, return `Self`. - pub fn layer_if (mut self, cond: DslVal, layer: DslVal) -> Self { - self.add_layer_if(Some(cond), layer); self - } - /// Add layer, return `&mut Self`. - pub fn add_layer (&mut self, layer: DslVal) -> &mut Self { - self.add_layer_if(None, layer.into()); self - } - /// Add condal layer, return `&mut Self`. - pub fn add_layer_if (&mut self, cond: Option>, bind: DslVal) -> &mut Self { - self.0.push(InputLayer { cond, bind }); - self - } - */ -} - - - - //let mut keys = iter.unwrap(); - //let mut map = InputMap::default(); - //while let Some(token) = keys.next() { - //if let Value::Exp(_, mut exp) = token.value { - //let next = exp.next(); - //if let Some(Token { value: Value::Key(sym), .. }) = next { - //match sym { - //"layer" => { - //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { - //let path = base.as_ref().parent().unwrap().join(unquote(path)); - //if !std::fs::exists(&path)? { - //return Err(format!("(e5) not found: {path:?}").into()) - //} - //map.add_layer(read_and_leak(path)?.into()); - //print!("layer:\n path: {:?}...", exp.0.0.trim()); - //println!("ok"); - //} else { - //return Err(format!("(e4) unexpected non-string {next:?}").into()) - //} - //}, - - //"layer-if" => { - //let mut cond = None; - - //if let Some(Token { value: Value::Sym(sym), .. }) = exp.next() { - //cond = Some(leak(sym)); - //} else { - //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - //}; - - //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { - //let path = base.as_ref().parent().unwrap().join(unquote(path)); - //if !std::fs::exists(&path)? { - //return Err(format!("(e5) not found: {path:?}").into()) - //} - //print!("layer-if:\n cond: {}\n path: {path:?}...", - //cond.unwrap_or_default()); - //let keys = read_and_leak(path)?.into(); - //let cond = cond.unwrap(); - //println!("ok"); - //map.add_layer_if( - //Box::new(move |state: &App|Take::take_or_fail( - //state, exp, ||"missing input layer conditional" - //)), keys - //); - //} else { - //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - //} - //}, - - //_ => return Err(format!("(e3) unexpected symbol {sym:?}").into()) - //} - //} else { - //return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()) - //} - //} else { - //return Err(format!("(e1) unexpected token {token:?}").into()) - //} - //} - //Ok(map) - //} -//{ -//} - //fn from (source: &'s str) -> Self { - //// this should be for single layer: - //use DslVal::*; - //let mut layers = vec![]; - //let mut source = CstIter::from(source); - //while let Some(Exp(_, mut iter)) = source.next().map(|x|x.value) { - //let mut iter = iter.clone(); - //layers.push(match iter.next().map(|x|x.value) { - //Some(Sym(sym)) if sym.starts_with("@") => InputLayer { - //cond: None, - //bind: vec![[ - //dsl_val(source.nth(1).unwrap()), - //dsl_val(source.nth(2).unwrap()), - //]], - //}, - //Some(Str(layer)) => InputLayer { - //cond: None, - //bind: dsl_val(source.nth(1).unwrap()), - //}, - //Some(Key("if")) => InputLayer { - //cond: Some(dsl_val(source.nth(1).unwrap())), - //bind: dsl_val(source.nth(2).unwrap()), - //}, - //_ => panic!("invalid token in keymap"), - //}) - //} - //Self(layers) - //} -//} - -fn unquote (x: &str) -> &str { - let mut chars = x.chars(); - chars.next(); - //chars.next_back(); - chars.as_str() -} - -fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { - Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) -} - -fn leak (x: impl AsRef) -> &'static str { - Box::leak(x.as_ref().into()) -} diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs index a993760..984bd4e 100644 --- a/dsl/src/cst.rs +++ b/dsl/src/cst.rs @@ -5,10 +5,10 @@ use crate::*; /// CST stores strings as source references and expressions as [CstIter] instances. #[derive(Debug, Clone, Default, PartialEq)] -pub struct Cst<'src>(pub CstIter<'src>); -impl<'src> Dsl for Cst<'src> { - type Str = &'src str; - type Exp = CstIter<'src>; +pub struct Cst<'s>(pub CstIter<'s>); +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = CstIter<'s>; fn nth (&self, index: usize) -> Option> { self.0.nth(index) } @@ -16,49 +16,49 @@ impl<'src> Dsl for Cst<'src> { /// Parsed substring with range and value. #[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstVal<'src> { +pub struct CstVal<'s> { /// Meaning of token. - pub value: DslVal<&'src str, CstIter<'src>>, + pub value: DslVal<&'s str, CstIter<'s>>, /// Reference to source text. - pub source: &'src str, + pub source: &'s str, /// Index of 1st character of token. pub start: usize, /// Length of token. pub length: usize, } -impl<'src> Dsl for CstVal<'src> { - type Str = &'src str; - type Exp = CstIter<'src>; +impl<'s> Dsl for CstVal<'s> { + type Str = &'s str; + type Exp = CstIter<'s>; fn nth (&self, index: usize) -> Option> { todo!() } } -impl<'src> CstVal<'src> { +impl<'s> CstVal<'s> { pub const fn new ( - source: &'src str, + source: &'s str, start: usize, length: usize, - value: DslVal<&'src str, CstIter<'src>> + value: DslVal<&'s str, CstIter<'s>> ) -> Self { Self { source, start, length, value } } pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } - pub const fn slice (&'src self) -> &'src str { + pub const fn slice (&'s self) -> &'s str { self.slice_source(self.source) } - pub const fn slice_source <'range> (&'src self, source: &'range str) -> &'range str { + pub const fn slice_source <'range> (&'s self, source: &'range str) -> &'range str { str_range(source, self.start, self.end()) } - pub const fn slice_source_exp <'range> (&'src self, source: &'range str) -> &'range str { + pub const fn slice_source_exp <'range> (&'s self, source: &'range str) -> &'range str { str_range(source, self.start.saturating_add(1), self.end()) } - pub const fn with_value (self, value: DslVal<&'src str, CstIter<'src>>) -> Self { + pub const fn with_value (self, value: DslVal<&'s str, CstIter<'s>>) -> Self { Self { value, ..self } } - pub const fn value (&self) -> DslVal<&'src str, CstIter<'src>> { + pub const fn value (&self) -> DslVal<&'s str, CstIter<'s>> { self.value } pub const fn error (self, error: DslErr) -> Self { @@ -121,46 +121,37 @@ impl<'src> CstVal<'src> { /// [Cst::next] returns just the [Cst] and mutates `self`, /// instead of returning an updated version of the struct as [CstConstIter::next] does. #[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstIter<'src>(pub CstConstIter<'src>); -impl<'src> Dsl for CstIter<'src> { - type Str = &'src str; +pub struct CstIter<'s>(pub CstConstIter<'s>); +impl<'s> Dsl for CstIter<'s> { + type Str = &'s str; type Exp = Self; fn nth (&self, index: usize) -> Option> { - use DslVal::*; - self.0.nth(index).map(|x|match x { - Nil => Nil, - Err(e) => Err(e), - Num(u) => Num(u), - Sym(s) => Sym(s), - Key(s) => Sym(s), - Str(s) => Sym(s), - Exp(d, x) => DslVal::Exp(d, CstIter(x)), - }) + self.0.nth(index).map(|x|dsl_val(x)) } } -impl<'src> CstIter<'src> { - pub const fn new (source: &'src str) -> Self { +impl<'s> CstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(CstConstIter::new(source)) } - pub const fn peek (&self) -> Option> { + pub const fn peek (&self) -> Option> { self.0.peek() } } -impl<'src> Iterator for CstIter<'src> { - type Item = CstVal<'src>; - fn next (&mut self) -> Option> { +impl<'s> Iterator for CstIter<'s> { + type Item = CstVal<'s>; + fn next (&mut self) -> Option> { self.0.next().map(|(item, rest)|{ self.0 = rest; item }) } } -impl<'src> Into>> for CstIter<'src> { - fn into (self) -> Vec> { +impl<'s> Into>> for CstIter<'s> { + fn into (self) -> Vec> { self.collect() } } -impl<'src> Into> for CstIter<'src> { +impl<'s> Into> for CstIter<'s> { fn into (self) -> Vec { self.map(Into::into).collect() } @@ -172,9 +163,9 @@ impl<'src> Into> for CstIter<'src> { /// * the source text remaining /// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? #[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstConstIter<'src>(pub &'src str); -impl<'src> Dsl for CstConstIter<'src> { - type Str = &'src str; +pub struct CstConstIter<'s>(pub &'s str); +impl<'s> Dsl for CstConstIter<'s> { + type Str = &'s str; type Exp = Self; fn nth (&self, mut index: usize) -> Option> { use DslVal::*; @@ -182,37 +173,39 @@ impl<'src> Dsl for CstConstIter<'src> { for i in 0..index { iter = iter.next()?.1 } - iter.next().map(|(x, _)|match x.value { - Nil => Nil, - Err(e) => Err(e), - Num(u) => Num(u), - Sym(s) => Sym(s), - Key(s) => Sym(s), - Str(s) => Sym(s), - Exp(d, x) => DslVal::Exp(d, x.0), - }) + iter.next().map(|(x, _)|dsl_val(x.value)) } } -impl<'src> CstConstIter<'src> { - pub const fn new (source: &'src str) -> Self { +impl<'s> CstConstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(source) } pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } - pub const fn next (mut self) -> Option<(CstVal<'src>, Self)> { + pub const fn next (mut self) -> Option<(CstVal<'s>, Self)> { Self::next_mut(&mut self) } - pub const fn peek (&self) -> Option> { + pub const fn peek (&self) -> Option> { peek_src(self.0) } - pub const fn next_mut (&mut self) -> Option<(CstVal<'src>, Self)> { + pub const fn next_mut (&mut self) -> Option<(CstVal<'s>, Self)> { match self.peek() { Some(token) => Some((token, self.chomp(token.end()))), None => None } } } +impl<'s> From> for CstIter<'s> { + fn from (iter: CstConstIter<'s>) -> Self { + Self(iter) + } +} +impl<'s> From> for CstConstIter<'s> { + fn from (iter: CstIter<'s>) -> Self { + iter.0 + } +} /// Implement the const iterator pattern. macro_rules! const_iter { @@ -229,8 +222,8 @@ macro_rules! const_iter { } } -const_iter!(<'src>|self: CstConstIter<'src>| - => CstVal<'src> +const_iter!(<'s>|self: CstConstIter<'s>| + => CstVal<'s> => self.next_mut().map(|(result, _)|result)); /// Static iteration helper used by [cst]. @@ -244,9 +237,9 @@ macro_rules! iterate { } } -pub const fn peek_src <'src> (source: &'src str) -> Option> { +pub const fn peek_src <'s> (source: &'s str) -> Option> { use DslVal::*; - let mut token: CstVal<'src> = CstVal::new(source, 0, 0, Nil); + let mut token: CstVal<'s> = CstVal::new(source, 0, 0, Nil); iterate!(char_indices(source) => (start, c) => token = match token.value() { Err(_) => return Some(token), Nil => match c { diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index b2733e0..00f83b3 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -42,7 +42,20 @@ pub enum DslVal { Exp(usize, Exp), } -pub trait Dsl { +pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { + use DslVal::*; + match val { + Nil => Nil, + Err(e) => Err(e), + Num(u) => Num(u), + Sym(s) => Sym(s.into()), + Key(s) => Key(s.into()), + Str(s) => Str(s.into()), + Exp(d, x) => Exp(d, x.into()), + } +} + +pub trait Dsl: Debug { type Str: PartialEq + Clone + Default + Debug + AsRef; type Exp: PartialEq + Clone + Default + Debug + Dsl; fn nth (&self, index: usize) -> Option>; @@ -61,7 +74,7 @@ impl< fn val (&self) -> DslVal { self.clone() } - fn nth (&self, index: usize) -> Option> { + fn nth (&self, _index: usize) -> Option> { todo!() } } @@ -163,16 +176,3 @@ from_str!(Ast|source|Self::from(CstIter::from(source))); from_str!(Cst<'s>|source|Self(CstIter(CstConstIter(source)))); from_str!(CstIter<'s>|source|Self(CstConstIter(source))); from_str!(CstConstIter<'s>|source|Self::new(source)); - -pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { - use DslVal::*; - match val { - Nil => Nil, - Err(e) => Err(e), - Num(u) => Num(u), - Sym(s) => Sym(s.into()), - Key(s) => Key(s.into()), - Str(s) => Str(s.into()), - Exp(d, x) => Exp(d, x.into()), - } -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index aa255fc..264fa19 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -5,8 +5,6 @@ pub(crate) use ::tengri_core::*; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; pub(crate) use std::collections::VecDeque; -pub(crate) use std::path::Path; -pub(crate) use std::fs::exists; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; diff --git a/editor/Cargo.toml b/editor/Cargo.toml new file mode 100644 index 0000000..692cf03 --- /dev/null +++ b/editor/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "tengri_editor" +description = "Embeddable editor for Tengri DSL." +version = { workspace = true } +edition = { workspace = true } diff --git a/vin/Cargo.toml b/editor/src/main.rs similarity index 100% rename from vin/Cargo.toml rename to editor/src/main.rs diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index e642490..363efc0 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,11 +1,22 @@ use crate::*; -/// A collection of input bind. +/// A collection of input bindings. /// /// Each contained layer defines a mapping from input event to command invocation /// over a given state. Furthermore, each layer may have an associated cond, /// so that only certain layers are active at a given time depending on state. -#[derive(Debug, Default)] pub struct InputMap(std::collections::BTreeMap); -impl InputMap { +#[derive(Debug, Default)] pub struct InputMap( + /// Map of input event (key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> +); +#[derive(Debug, Default)] pub struct InputBinding { + condition: Option, + command: T, + description: Option>, + source: Option>, +} +impl InputMap { /// Create input layer collection from path to text file. pub fn from_path > (path: P) -> Usually { if !exists(path.as_ref())? { @@ -18,29 +29,38 @@ impl InputMap { Self::from_dsl(CstIter::from(source.as_ref())) } /// Create input layer collection from DSL. - pub fn from_dsl (dsl: impl Dsl) -> Usually { + pub fn from_dsl (dsl: D) -> Usually { use DslVal::*; - let mut input_map: Self = Self(Default::default()); - while let Exp(_, mut exp) = dsl.val() { + let mut input_map: BTreeMap>> = Default::default(); + let mut index = 0; + while let Some(Exp(_, mut exp)) = dsl.nth(index) { let val = exp.nth(0).map(|x|x.val()); match val { Some(Str(path)) => { - let path = PathBuf::from(path.as_ref()); + let path = PathBuf::from(path.as_ref()); let module = InputMap::::from_path(&path)?; for (key, val) in module.0.into_iter() { + todo!("import {exp:?} {key:?} {val:?} {path:?}"); + if !input_map.contains_key(&key) { + input_map.insert(key, vec![]); + } } }, - Some(Exp(_, expr)) if let Some(Sym(sym)) = expr.nth(0) => { - //input_map.unconditional.push(expr); - todo!("binding"); + Some(Sym(sym)) => { + //let key: I = sym.into(); + //if !input_map.contains_key(&key) { + //input_map.insert(key, vec![]); + //} + todo!("binding {exp:?} {sym:?}"); }, - Some(Exp(_, expr)) if let Some(Key(key)) = expr.nth(0) && key.as_ref() == "if" => { - todo!("conditional binding"); + Some(Key(key)) if key.as_ref() == "if" => { + todo!("conditional binding {exp:?} {key:?}"); }, _ => return Result::Err(format!("invalid token in keymap: {val:?}").into()), } + index += 1; } - Ok(input_map) + Ok(Self(input_map)) } /// Evaluate the active layers for a given state, /// returning the command to be executed, if any. diff --git a/input/src/lib.rs b/input/src/lib.rs index 86c6150..0779092 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -2,7 +2,8 @@ #![feature(if_let_guard)] pub(crate) use std::fmt::Debug; -pub(crate) use std::collections::BTreeMap; +pub(crate) use std::sync::Arc; +pub(crate) use std::collections::{BTreeMap, HashMap}; pub(crate) use std::path::{Path, PathBuf}; pub(crate) use std::fs::exists; pub(crate) use tengri_core::*; diff --git a/tengri/src/test.rs b/tengri/src/test.rs index 025e522..c782996 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,9 +1,15 @@ use crate::*; use crate::{dsl::*, input::*, tui::TuiIn}; use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; +use std::cmp::Ordering; #[test] fn test_subcommand () -> Usually<()> { - struct Test { keys: InputMap<(), Ast> } + #[derive(Debug)] struct Event(crossterm::event::Event); + impl Eq for Event {} + impl PartialEq for Event { fn eq (&self, other: &Self) -> bool { todo!() } } + impl Ord for Event { fn cmp (&self, other: &Self) -> Ordering { todo!() } } + impl PartialOrd for Event { fn partial_cmp (&self, other: &Self) -> Option { None } } + struct Test { keys: InputMap } handle!(TuiIn: |self: Test, input|Ok(None));/*if let Some(command) = self.keys.command(self, input) { Ok(Some(true)) diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 3bf7791..9ee6be6 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -8,13 +8,13 @@ pub struct TuiIn( /// Exit flag pub Arc, /// Input event - pub Event, + pub crossterm::event::Event, ); impl Input for TuiIn { - type Event = Event; + type Event = crossterm::event::Event; type Handled = bool; - fn event (&self) -> &Event { &self.1 } + fn event (&self) -> &crossterm::event::Event { &self.1 } fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } fn done (&self) { self.0.store(true, Relaxed); } } From 38d29f30a772438c96187c7689ed975067c1671b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 14 Jul 2025 23:06:17 +0300 Subject: [PATCH 2/4] fix(proc): expose variants --- proc/src/proc_expose.rs | 92 +++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 98b0df0..d0f643b 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -60,37 +60,7 @@ impl ToTokens for ExposeImpl { let state = &block.self_ty; write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { - let formatted_type = format!("{}", quote! { #t }); - let predefined = match formatted_type.as_str() { - "bool" => quote! { - Some(::tengri::dsl::DslVal::Sym(":true")) => true, - Some(::tengri::dsl::DslVal::Sym(":false")) => false, - }, - "u8" | "u16" | "u32" | "u64" | "usize" | - "i8" | "i16" | "i32" | "i64" | "isize" => { - let num_err = LitStr::new( - &format!("{{n}}: failed to convert to {formatted_type}"), - Span::call_site() - ); - quote! { - Some(::tengri::dsl::DslVal::Num(n)) => TryInto::<#t>::try_into(*n) - .unwrap_or_else(|_|panic!(#num_err)), - } - }, - _ => quote! {}, - }; - let values = variants.iter().map(|(key, value)|{ - let key = LitStr::new(&key, Span::call_site()); - quote! { Some(::tengri::dsl::DslVal::Sym(#key)) => state.#value(), } - }); - write_quote_to(out, quote! { - /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::DslFrom<#state> for #t { - fn try_dsl_from (state: &#state, value: &impl Dsl) -> Perhaps { - Ok(Some(match value { #predefined #(#values)* _ => return Ok(None) })) - } - } - }); + self.expose_variants(out, t, variants); } if exposed.len() > 0 { //panic!("{}", quote! {#out}); @@ -98,6 +68,66 @@ impl ToTokens for ExposeImpl { } } +impl ExposeImpl { + fn expose_variants ( + &self, out: &mut TokenStream2, t: &ExposeType, variants: &BTreeMap + ) { + let Self(ItemImpl { self_ty: state, .. }, ..) = self; + let arms = variants.iter().map(|(key, value)|{ + let key = LitStr::new(&key, Span::call_site()); + quote! { #key => state.#value(), } + }); + let arms = Self::with_predefined(t, quote! { #(#arms)* }); + write_quote_to(out, quote! { + /// Generated by [tengri_proc::expose]. + impl ::tengri::dsl::DslFrom<#state> for #t { + fn try_dsl_from (state: &#state, dsl: &impl Dsl) -> Perhaps { + Ok(Some(match dsl.val() { + #arms + _ => { return Ok(None) } + })) + } + } + }); + } + fn with_predefined (t: &ExposeType, variants: impl ToTokens) -> impl ToTokens { + let formatted_type = format!("{}", quote! { #t }); + if &formatted_type == "bool" { + return quote! { + ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + ":true" => true, + ":false" => false, + #variants + _ => { return Ok(None) } + }, + } + } + if matches!(formatted_type.as_str(), + "u8" | "u16" | "u32" | "u64" | "usize" | + "i8" | "i16" | "i32" | "i64" | "isize") + { + let num_err = LitStr::new( + &format!("{{n}}: failed to convert to {formatted_type}"), + Span::call_site() + ); + return quote! { + ::tengri::dsl::DslVal::Num(n) => TryInto::<#t>::try_into(n) + .unwrap_or_else(|_|panic!(#num_err)), + ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + #variants + _ => { return Ok(None) } + }, + } + } + return quote! { + ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + #variants + _ => { return Ok(None) } + }, + } + } +} + impl From for ExposeSym { fn from (this: LitStr) -> Self { Self(this) } } impl PartialOrd for ExposeSym { From ca4c558eab46aad27da145381e425908d995a625 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 14 Jul 2025 23:06:29 +0300 Subject: [PATCH 3/4] fix(input): InputMap manual Default impl --- input/src/input_dsl.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 363efc0..7ac0644 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -4,12 +4,17 @@ use crate::*; /// Each contained layer defines a mapping from input event to command invocation /// over a given state. Furthermore, each layer may have an associated cond, /// so that only certain layers are active at a given time depending on state. -#[derive(Debug, Default)] pub struct InputMap( +#[derive(Debug)] pub struct InputMap( /// Map of input event (key combination) to /// all command expressions bound to it by /// all loaded input layers. pub BTreeMap>> ); +impl Default for InputMap { + fn default () -> Self { + Self(Default::default()) + } +} #[derive(Debug, Default)] pub struct InputBinding { condition: Option, command: T, From e72225f83c89a2bc9c6bee9b00373dfa530e1909 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 16 Jul 2025 00:10:03 +0300 Subject: [PATCH 4/4] wip: fix dsl --- Cargo.toml | 1 + dsl/Cargo.toml | 1 + dsl/src/ast.rs | 38 ---- dsl/src/cst.rs | 312 -------------------------------- dsl/src/dsl.rs | 178 ------------------- dsl/src/lib.rs | 371 ++++++++++++++++++++++++++++++++++++++- dsl/src/test.rs | 18 +- output/src/ops.rs | 8 +- proc/src/proc_command.rs | 6 +- proc/src/proc_expose.rs | 5 +- proc/src/proc_view.rs | 57 +++--- 11 files changed, 421 insertions(+), 574 deletions(-) delete mode 100644 dsl/src/ast.rs delete mode 100644 dsl/src/cst.rs delete mode 100644 dsl/src/dsl.rs diff --git a/Cargo.toml b/Cargo.toml index a7bb2eb..fa89dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ edition = "2024" [workspace.dependencies] atomic_float = { version = "1" } better-panic = { version = "0.3.0" } +const_panic = { version = "0.2.12", features = [ "derive" ] } crossterm = { version = "0.28.1" } heck = { version = "0.5" } itertools = { version = "0.14.0" } diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index ab0bc37..256b599 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -7,6 +7,7 @@ edition = { workspace = true } [dependencies] tengri_core = { path = "../core" } konst = { workspace = true } +const_panic = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } diff --git a/dsl/src/ast.rs b/dsl/src/ast.rs deleted file mode 100644 index ac85d87..0000000 --- a/dsl/src/ast.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! The abstract syntax tree (AST) can be produced from the CST -//! by cloning source slices into owned ([Arc]) string slices. -use crate::*; - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(pub Arc, Ast>>>); - -impl Dsl for Ast { - type Str = Arc; - type Exp = Ast; - fn nth (&self, index: usize) -> Option> { - self.0.get(index).cloned() - } -} - -impl<'s> From> for Ast { - fn from (cst: Cst<'s>) -> Self { - Self(VecDeque::from([dsl_val(cst.val())]).into()) - } -} - -impl<'s> From> for Ast { - fn from (cst: CstIter<'s>) -> Self { - Self(cst.map(|x|x.value.into()).collect::>().into()) - } -} - -impl<'s> From> for Ast { - fn from (cst: CstVal<'s>) -> Self { - Self(VecDeque::from([dsl_val(cst.val())]).into()) - } -} - -impl<'s> From>> for DslVal, Ast> { - fn from (cst: DslVal<&'s str, CstIter<'s>>) -> Self { - dsl_val(cst) - } -} diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs deleted file mode 100644 index 984bd4e..0000000 --- a/dsl/src/cst.rs +++ /dev/null @@ -1,312 +0,0 @@ -//! The concrete syntax tree (CST) implements zero-copy -//! parsing of the DSL from a string reference. CST items -//! preserve info about their location in the source. -use crate::*; - -/// CST stores strings as source references and expressions as [CstIter] instances. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Cst<'s>(pub CstIter<'s>); -impl<'s> Dsl for Cst<'s> { - type Str = &'s str; - type Exp = CstIter<'s>; - fn nth (&self, index: usize) -> Option> { - self.0.nth(index) - } -} - -/// Parsed substring with range and value. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstVal<'s> { - /// Meaning of token. - pub value: DslVal<&'s str, CstIter<'s>>, - /// Reference to source text. - pub source: &'s str, - /// Index of 1st character of token. - pub start: usize, - /// Length of token. - pub length: usize, -} -impl<'s> Dsl for CstVal<'s> { - type Str = &'s str; - type Exp = CstIter<'s>; - fn nth (&self, index: usize) -> Option> { - todo!() - } -} - -impl<'s> CstVal<'s> { - pub const fn new ( - source: &'s str, - start: usize, - length: usize, - value: DslVal<&'s str, CstIter<'s>> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'s self) -> &'s str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'s self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'s self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: DslVal<&'s str, CstIter<'s>>) -> Self { - Self { value, ..self } - } - pub const fn value (&self) -> DslVal<&'s str, CstIter<'s>> { - self.value - } - pub const fn error (self, error: DslErr) -> Self { - Self { value: DslVal::Err(error), ..self } - } - pub const fn grow (self) -> Self { - Self { length: self.length.saturating_add(1), ..self } - } - pub const fn grow_num (self, m: usize, c: char) -> Self { - match to_digit(c) { - Result::Ok(n) => Self { value: DslVal::Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: DslVal::Err(e), ..self.grow() }, - } - } - pub const fn grow_key (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Key(token.slice_source(self.source))) - } - pub const fn grow_sym (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Sym(token.slice_source(self.source))) - } - pub const fn grow_str (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Str(token.slice_source(self.source))) - } - pub const fn grow_exp (self) -> Self { - let token = self.grow(); - if let DslVal::Exp(depth, _) = token.value() { - token.with_value(DslVal::Exp(depth, CstIter::new(token.slice_source_exp(self.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let DslVal::Exp(depth, source) = token.value() { - token.with_value(DslVal::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let DslVal::Exp(depth, source) = token.value() { - if depth > 0 { - token.with_value(DslVal::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} - -/// Provides a native [Iterator] API over [CstConstIter], -/// emitting [Cst] items. -/// -/// [Cst::next] returns just the [Cst] and mutates `self`, -/// instead of returning an updated version of the struct as [CstConstIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstIter<'s>(pub CstConstIter<'s>); -impl<'s> Dsl for CstIter<'s> { - type Str = &'s str; - type Exp = Self; - fn nth (&self, index: usize) -> Option> { - self.0.nth(index).map(|x|dsl_val(x)) - } -} -impl<'s> CstIter<'s> { - pub const fn new (source: &'s str) -> Self { - Self(CstConstIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} -impl<'s> Iterator for CstIter<'s> { - type Item = CstVal<'s>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{ - self.0 = rest; - item - }) - } -} -impl<'s> Into>> for CstIter<'s> { - fn into (self) -> Vec> { - self.collect() - } -} -impl<'s> Into> for CstIter<'s> { - fn into (self) -> Vec { - self.map(Into::into).collect() - } -} - -/// Owns a reference to the source text. -/// [CstConstIter::next] emits subsequent pairs of: -/// * a [Cst] and -/// * the source text remaining -/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstConstIter<'s>(pub &'s str); -impl<'s> Dsl for CstConstIter<'s> { - type Str = &'s str; - type Exp = Self; - fn nth (&self, mut index: usize) -> Option> { - use DslVal::*; - let mut iter = self.clone(); - for i in 0..index { - iter = iter.next()?.1 - } - iter.next().map(|(x, _)|dsl_val(x.value)) - } -} -impl<'s> CstConstIter<'s> { - pub const fn new (source: &'s str) -> Self { - Self(source) - } - pub const fn chomp (&self, index: usize) -> Self { - Self(split_at(self.0, index).1) - } - pub const fn next (mut self) -> Option<(CstVal<'s>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(CstVal<'s>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} -impl<'s> From> for CstIter<'s> { - fn from (iter: CstConstIter<'s>) -> Self { - Self(iter) - } -} -impl<'s> From> for CstConstIter<'s> { - fn from (iter: CstIter<'s>) -> Self { - iter.0 - } -} - -/// Implement the const iterator pattern. -macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -const_iter!(<'s>|self: CstConstIter<'s>| - => CstVal<'s> - => self.next_mut().map(|(result, _)|result)); - -/// Static iteration helper used by [cst]. -macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -pub const fn peek_src <'s> (source: &'s str) -> Option> { - use DslVal::*; - let mut token: CstVal<'s> = CstVal::new(source, 0, 0, Nil); - iterate!(char_indices(source) => (start, c) => token = match token.value() { - Err(_) => return Some(token), - Nil => match c { - ' '|'\n'|'\r'|'\t' => - token.grow(), - '(' => - CstVal::new(source, start, 1, Exp(1, CstIter::new(str_range(source, start, start + 1)))), - '"' => - CstVal::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - CstVal::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - CstVal::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - CstVal::new(source, start, 1, match to_digit(c) { - Ok(c) => DslVal::Num(c), - Result::Err(e) => DslVal::Err(e) - }), - _ => token.error(Unexpected(c)) - }, - Str(_) => match c { - '"' => return Some(token), - _ => token.grow_str(), - }, - Num(n) => match c { - '0'..='9' => token.grow_num(n, c), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Sym(_) => match c { - 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Key(_) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Exp(depth, _) => match depth { - 0 => return Some(token.grow_exp()), - _ => match c { - ')' => token.grow_out(), - '(' => token.grow_in(), - _ => token.grow_exp(), - } - }, - }); - match token.value() { - Nil => None, - _ => Some(token), - } -} - -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs deleted file mode 100644 index 00f83b3..0000000 --- a/dsl/src/dsl.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::*; -use std::error::Error; - -/// Standard result type for DSL-specific operations. -pub type DslResult = Result; - -/// DSL-specific error codes. -#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum DslErr { - #[error("parse failed: not implemented")] - Unimplemented, - #[error("parse failed: empty")] - Empty, - #[error("parse failed: incomplete")] - Incomplete, - #[error("parse failed: unexpected character '{0}'")] - Unexpected(char), - #[error("parse failed: error #{0}")] - Code(u8), -} - -/// Enumeration of possible DSL tokens. -/// Generic over string and expression storage. -/// -/// * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] -/// * [DslVal::Exp] wraps an expression depth and a [CstIter] -/// with the remaining part of the expression. -/// * expression depth other that 0 mean unclosed parenthesis. -/// * closing and unopened parenthesis panics during reading. -/// * [ ] TODO: signed depth might be interesting -/// * [DslVal::Sym] and [DslVal::Key] are stringish literals -/// with slightly different parsing rules. -/// * [DslVal::Num] is an unsigned integer literal. -#[derive(Clone, Debug, PartialEq, Default)] -pub enum DslVal { - #[default] - Nil, - Err(DslErr), - Num(usize), - Sym(Str), - Key(Str), - Str(Str), - Exp(usize, Exp), -} - -pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { - use DslVal::*; - match val { - Nil => Nil, - Err(e) => Err(e), - Num(u) => Num(u), - Sym(s) => Sym(s.into()), - Key(s) => Key(s.into()), - Str(s) => Str(s.into()), - Exp(d, x) => Exp(d, x.into()), - } -} - -pub trait Dsl: Debug { - type Str: PartialEq + Clone + Default + Debug + AsRef; - type Exp: PartialEq + Clone + Default + Debug + Dsl; - fn nth (&self, index: usize) -> Option>; - fn val (&self) -> DslVal { - self.nth(0).unwrap_or(DslVal::Nil) - } - // exp-only nth here? -} - -impl< - Str: PartialEq + Clone + Default + Debug + AsRef, - Exp: PartialEq + Clone + Default + Debug + Dsl, -> Dsl for DslVal { - type Str = Str; - type Exp = Exp; - fn val (&self) -> DslVal { - self.clone() - } - fn nth (&self, _index: usize) -> Option> { - todo!() - } -} - -/// May construct self from state and DSL. -pub trait DslFrom: Sized { - fn try_dsl_from (state: &State, value: &impl Dsl) -> Perhaps; - fn dsl_from ( - state: &State, value: &impl Dsl, error: impl Fn()->Box - ) -> Usually { - match Self::try_dsl_from(state, value)? { - Some(value) => Ok(value), - _ => Err(error()) - } - } -} - -/// May construct another from self and DSL. -pub trait DslInto { - fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into ( - &self, value: &impl Dsl, error: impl Fn()->Box - ) -> Usually { - match Self::try_dsl_into(self, value)? { - Some(value) => Ok(value), - _ => Err(error()) - } - } -} - -impl DslVal { - pub fn is_nil (&self) -> bool { - matches!(self, Self::Nil) - } - pub fn as_err (&self) -> Option<&DslErr> { - if let Self::Err(e) = self { Some(e) } else { None } - } - pub fn as_num (&self) -> Option { - if let Self::Num(n) = self { Some(*n) } else { None } - } - pub fn as_exp (&self) -> Option<&Exp> { - if let Self::Exp(_, x) = self { Some(x) } else { None } - } - pub fn exp_depth (&self) -> Option { - todo!() - } - pub fn exp_head (&self) -> Option<&Self> { - todo!() - } // TODO - pub fn exp_tail (&self) -> Option<&Exp> { - todo!() - } // TODO - pub fn peek (&self) -> Option { - todo!() - } - pub fn next (&mut self) -> Option { - todo!() - } - pub fn rest (self) -> Vec { - todo!() - } -} - -impl Copy for DslVal {} - -impl, Exp> DslVal { - pub fn as_sym (&self) -> Option<&str> { - if let Self::Sym(s) = self { Some(s.as_ref()) } else { None } - } - pub fn as_key (&self) -> Option<&str> { - if let Self::Key(k) = self { Some(k.as_ref()) } else { None } - } - pub fn as_str (&self) -> Option<&str> { - if let Self::Str(s) = self { Some(s.as_ref()) } else { None } - } - pub fn exp_match (&self, namespace: &str, cb: F) -> Perhaps - where F: Fn(&str, &Exp)-> Perhaps { - if let Some(Self::Key(key)) = self.exp_head() - && key.as_ref().starts_with(namespace) - && let Some(tail) = self.exp_tail() { - cb(key.as_ref().split_at(namespace.len()).1, tail) - } else { - Ok(None) - } - } -} - -macro_rules! from_str { - ($Struct:ty |$source:ident| $expr:expr) => { - impl<'s> From<&'s str> for $Struct { - fn from ($source: &'s str) -> Self { - $expr - } - } - } -} - -from_str!(Ast|source|Self::from(CstIter::from(source))); -from_str!(Cst<'s>|source|Self(CstIter(CstConstIter(source)))); -from_str!(CstIter<'s>|source|Self(CstConstIter(source))); -from_str!(CstConstIter<'s>|source|Self::new(source)); diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 264fa19..3e26227 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,17 +1,376 @@ #![feature(adt_const_params)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] +#![feature(const_precise_live_drops)] +extern crate const_panic; +use const_panic::{concat_panic, PanicFmt}; pub(crate) use ::tengri_core::*; +pub(crate) use std::error::Error; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; pub(crate) use std::collections::VecDeque; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; -pub(crate) use self::DslErr::*; - -mod dsl; pub use self::dsl::*; -mod ast; pub use self::ast::*; -mod cst; pub use self::cst::*; - +pub(crate) use self::DslError::*; #[cfg(test)] mod test; + +pub type DslUsually = Result; +pub type DslPerhaps = Result, DslError>; + +/// Pronounced dizzle. +pub trait Dsl: Clone + Debug { + /// The string representation for a dizzle. + type Str: DslStr; + /// The expression representation for a dizzle. + type Exp: DslExp; + /// Return a token iterator for this dizzle. + fn dsl (&self) -> DslUsually<&Val>; +} + +/// Enumeration of values representable by a DSL [Token]s. +/// Generic over string and expression storage. +#[derive(Clone, Debug, PartialEq, Default)] +pub enum Val { + #[default] + Nil, + /// Unsigned integer literal + Num(usize), + /// Tokens that start with `:` + Sym(D::Str), + /// Tokens that don't start with `:` + Key(D::Str), + /// Quoted string literals + Str(D::Str), + /// Expressions. + Exp( + /// Expression depth checksum. Must be 0, otherwise you have an unclosed delimiter. + usize, + /// Expression content. + D::Exp + ), + Error(DslError), +} + +impl> Copy for Val {} + +impl Val { + pub fn convert (&self) -> Val where + B::Str: for<'a> From<&'a D::Str>, + B::Exp: for<'a> From<&'a D::Exp> + { + match self { Val::Nil => Val::Nil, + Val::Num(u) => Val::Num(*u), + Val::Sym(s) => Val::Sym(s.into()), + Val::Key(s) => Val::Key(s.into()), + Val::Str(s) => Val::Str(s.into()), + Val::Exp(d, x) => Val::Exp(*d, x.into()), + Val::Error(e) => Val::Error(*e) } } + pub fn is_nil (&self) -> bool { matches!(self, Self::Nil) } + pub fn as_error (&self) -> Option<&DslError> { if let Self::Error(e) = self { Some(e) } else { None } } + pub fn as_num (&self) -> Option {match self{Self::Num(n)=>Some(*n),_=>None}} + pub fn as_sym (&self) -> Option<&str> {match self{Self::Sym(s )=>Some(s.as_ref()),_=>None}} + pub fn as_key (&self) -> Option<&str> {match self{Self::Key(k )=>Some(k.as_ref()),_=>None}} + pub fn as_str (&self) -> Option<&str> {match self{Self::Str(s )=>Some(s.as_ref()),_=>None}} + pub fn as_exp (&self) -> Option<&D::Exp> {match self{Self::Exp(_, x)=>Some(x),_=>None}} + pub fn exp_depth (&self) -> Option { todo!() } + pub fn exp_head_tail (&self) -> (Option<&Self>, Option<&D::Exp>) { (self.exp_head(), self.exp_tail()) } + pub fn exp_head (&self) -> Option<&Self> { todo!() } // TODO + pub fn exp_tail (&self) -> Option<&D::Exp> { todo!() } + pub fn peek (&self) -> Option { todo!() } + pub fn next (&mut self) -> Option { todo!() } + pub fn rest (self) -> Vec { todo!() } + //pub fn exp_match (&self, namespace: &str, cb: F) -> DslPerhaps + //where F: Fn(&str, &Exp)-> DslPerhaps { + //if let Some(Self::Key(key)) = self.exp_head() + //&& key.as_ref().starts_with(namespace) + //&& let Some(tail) = self.exp_tail() { + //cb(key.as_ref().split_at(namespace.len()).1, tail) + //} else { + //Ok(None) + //} + //} +} + +/// The string representation for a [Dsl] implementation. +/// [Cst] uses `&'s str`. [Ast] uses `Arc`. +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} +impl + std::ops::Deref> DslStr for T {} + +/// The expression representation for a [Dsl] implementation. +/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. +pub trait DslExp: PartialEq + Clone + Default + Debug {} +impl DslExp for T {} + +/// The abstract syntax tree (AST) can be produced from the CST +/// by cloning source slices into owned ([Arc]) string slices. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Ast(Token); +impl Dsl for Ast { + type Str = Arc; + type Exp = VecDeque>>; + fn dsl (&self) -> DslUsually<&Val> { + Ok(self.0.value()) + } +} + +/// The concrete syntax tree (CST) implements zero-copy +/// parsing of the DSL from a string reference. CST items +/// preserve info about their location in the source. +/// CST stores strings as source references and expressions as [CstIter] instances. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Cst<'s>(Token>); +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = CstConstIter<'s>; + fn dsl (&self) -> DslUsually<&Val> { + Ok(self.0.value()) + } +} + +/// `State` + [Dsl] -> `Self`. +pub trait FromDsl: Sized { + fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; + fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_from_dsl(state, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } + +/// `self` + `Options` -> [Dsl] +pub trait IntoDsl { /*TODO*/ } + +/// `self` + [Dsl] -> `Item` +pub trait DslInto { + fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } + +/// `self` + `Item` -> [Dsl] +pub trait DslFrom { /*TODO*/ } +/// Standard result type for DSL-specific operations. +pub type DslResult = Result; +/// DSL-specific error codes. +#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { + #[error("parse failed: not implemented")] + Unimplemented, + #[error("parse failed: empty")] + Empty, + #[error("parse failed: incomplete")] + Incomplete, + #[error("parse failed: unexpected character '{0}'")] + Unexpected(char), + #[error("parse failed: error #{0}")] + Code(u8), + #[error("end reached")] + End +} +/// Provides native [Iterator] API over [CstConstIter], emitting [Cst] items. +/// +/// [Cst::next] returns just the [Cst] and mutates `self`, +/// instead of returning an updated version of the struct as [CstConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstIter<'s>(CstConstIter<'s>); +impl<'s> CstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(CstConstIter::new(source)) } +} +impl<'s> Iterator for CstIter<'s> { + type Item = Token>; + fn next (&mut self) -> Option { + match self.0.advance() { + Ok(Some((item, rest))) => { self.0 = rest; item.into() }, + Ok(None) => None, + Err(e) => panic!("{e:?}") + } + } +} +/// Holds a reference to the source text. +/// [CstConstIter::next] emits subsequent pairs of: +/// * a [Cst] and +/// * the source text remaining +/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstConstIter<'s>(pub &'s str); +impl<'s> From <&'s str> for CstConstIter<'s> { + fn from (src: &'s str) -> Self { Self(src) } +} +impl<'s> Iterator for CstConstIter<'s> { + type Item = Token>; + fn next (&mut self) -> Option>> { self.advance().unwrap().map(|x|x.0) } +} +impl<'s> ConstIntoIter for CstConstIter<'s> { + type Kind = IsIteratorKind; + type Item = Cst<'s>; + type IntoIter = Self; +} +impl<'s> CstConstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(source) } + pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } + pub const fn peek (&self) -> DslPerhaps>> { Token::peek(self.0) } + //pub const fn next (mut self) -> Option<(Token>, Self)> { + //Self::advance(&mut self).unwrap() } + pub const fn advance (&mut self) -> DslPerhaps<(Token>, Self)> { + match self.peek() { + Ok(Some(token)) => { + let end = self.chomp(token.span.end()); + Ok(Some((token.copy(), end))) + }, + Ok(None) => Ok(None), + Err(e) => Err(e) + } + } +} + +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct Span { + /// Reference to source text. + pub source: D::Str, + /// Index of 1st character of span. + pub start: usize, + /// Length of span. + pub length: usize, +} +impl<'s, D: Dsl> Span { + pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } +} +impl<'s, D: Dsl> Span { + pub const fn slice (&self) -> &'s str { + str_range(self.source, self.start, self.end()) } + pub const fn slice_exp (&self) -> &'s str { + str_range(self.source, self.start.saturating_add(1), self.end()) } + pub const fn grow (&mut self) -> DslUsually<&mut Self> { + if self.length + self.start >= self.source.len() { return Err(End) } + self.length = self.length.saturating_add(1); + Ok(self) } +} + +/// Parsed substring with range and value. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Token { + /// Source span of token. + span: Span, + /// Meaning of token. + value: Val, +} +impl Token { + pub const fn value (&self) -> &Val { &self.value } + pub const fn span (&self) -> &Span { &self.span } + pub const fn err (&self) -> Option { + if let Val::Error(e) = self.value { Some(e) } else { None } } + pub const fn new (source: D::Str, start: usize, length: usize, value: Val) -> Self { + Self { value, span: Span { source, start, length } } } + pub const fn copy (&self) -> Self where D::Str: Copy, D::Exp: Copy { + Self { span: Span { ..self.span }, value: self.value } } +} +const fn or_panic (result: DslUsually) -> T { + match result { Ok(t) => t, Err(e) => const_panic::concat_panic!(e) } +} +impl<'s, D: Dsl> Token { + pub const fn peek (src: D::Str) -> DslPerhaps where D::Exp: From<&'s str> { + use Val::*; + let mut t = Self::new(src, 0, 0, Nil); + let mut iter = char_indices(src); + while let Some(((i, c), next)) = iter.next() { + t = match (t.value(), c) { + (Error(_), _) => + return Ok(Some(t)), + (Nil, ' '|'\n'|'\r'|'\t') => + *or_panic(t.grow()), + (Nil, '(') => + Self::new(src, i, 1, Exp(1, D::Exp::from(str_range(src, i, i + 1)))), + (Nil, '"') => + Self::new(src, i, 1, Str(str_range(src, i, i + 1))), + (Nil, ':'|'@') => + Self::new(src, i, 1, Sym(str_range(src, i, i + 1))), + (Nil, '/'|'a'..='z') => + Self::new(src, i, 1, Key(str_range(src, i, i + 1))), + (Nil, '0'..='9') => + Self::new(src, i, 1, match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }), + (Nil, _) => + { t.value = Val::Error(Unexpected(c)); t }, + (Str(_), '"') => + return Ok(Some(t)), + (Str(_), _) => + { or_panic(t.grow()); t.value = Str(t.span.slice()); t }, + (Num(m), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Num(m), _) => match to_digit(c) { + Ok(n) => { t.grow()?; t.value = Num(10*m+n); t }, + Err(e) => { t.grow()?; t.value = Error(e); t } }, + (Sym(_), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Sym(_), 'a'..='z'|'A'..='Z'|'0'..='9'|'-') => { + t.grow()?; t.value = Sym(t.span.slice()); t }, + (Sym(_), _) => + { t.value = Error(Unexpected(c)); t }, + (Key(_), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Key(_), 'a'..='z'|'0'..='9'|'-'|'/') => + { t.grow()?; t.value = Key(t.span.slice()); t }, + (Key(_), _ ) => + { t.value = Error(Unexpected(c)); t }, + (Exp(0, _), _) => + { t.grow()?; t.value = Exp(0, D::Exp::from(t.span.slice_exp())); return Ok(Some(t)) }, + (Exp(d, _), ')') => + { t.grow()?; t.value = Exp(d-1, D::Exp::from(t.span.slice_exp())); t }, + (Exp(d, _), '(') => + { t.grow()?; t.value = Exp(d+1, D::Exp::from(t.span.slice_exp())); t }, + (Exp(d, _), _ ) => + { t.grow()?; t.value = Exp(*d, D::Exp::from(t.span.slice_exp())); t }, + }; + iter = next; + } + Ok(match t.value() { + Nil => None, + _ => Some(t) + }) + } + pub const fn grow (&mut self) -> DslUsually<&mut Self> { self.span.grow()?; Ok(self) } + pub const fn grow_exp (&mut self, d: isize) -> &mut Self where D::Exp: From<&'s str> { + if let Val::Exp(depth, _) = self.value() { + self.value = Val::Exp((*depth as isize + d) as usize, D::Exp::from(self.span.slice_exp())); + self + } else { + unreachable!() + } + } +} + +pub const fn to_digit (c: char) -> DslResult { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) }) } + +macro_rules! iterate(($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; while let Some(($arg, next)) = iter.next() { $body; iter = next; } }); +pub const fn to_number (digits: &str) -> DslResult { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) }); + Ok(value) } + +/// Implement type conversions. +macro_rules! from(($($Struct:ty { $( + $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr +);+ $(;)? })*) => { $( + $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { + fn from ($source: $From) -> Self { $expr } + })+ +)* }); + +//from! { + ////Vec> { <'s> (val: CstIter<'s>) val.collect(); } + //CstConstIter<'s> { + //<'s> (src: &'s str) Self(src); + //<'s> (iter: CstIter<'s>) iter.0; } + //CstIter<'s> { + //<'s> (src: &'s str) Self(CstConstIter(src)); + //<'s> (iter: CstConstIter<'s>) Self(iter); } + //Cst<'s> { <'s> (src: &'s str) Self(CstIter(CstConstIter(src))); } + //Vec { <'s> (val: CstIter<'s>) val.map(Into::into).collect(); } + //Token> { <'s> (token: Token>) Self { value: token.value.into(), span: token.span.into() } } + //Ast { + //<'s> (src: &'s str) Ast::from(CstIter(CstConstIter(src))); + //<'s> (cst: Cst<'s>) Ast(VecDeque::from([dsl_val(cst.val())]).into()); + //<'s> (iter: CstIter<'s>) Ast(iter.map(|x|x.value.into()).collect::>().into()); + // (token: Token) Ast(VecDeque::from([dsl_val(token.val())]).into()); } +//} diff --git a/dsl/src/test.rs b/dsl/src/test.rs index b275573..dfb37f4 100644 --- a/dsl/src/test.rs +++ b/dsl/src/test.rs @@ -12,10 +12,10 @@ use proptest::prelude::*; } #[test] fn test_num () { - let _digit = to_digit('0'); - let _digit = to_digit('x'); - let _number = to_number(&"123"); - let _number = to_number(&"12asdf3"); + let _digit = Token::to_digit('0'); + let _digit = Token::to_digit('x'); + let _number = Token::to_number(&"123"); + let _number = Token::to_number(&"12asdf3"); } //proptest! { //#[test] fn proptest_source_iter ( @@ -48,15 +48,15 @@ use proptest::prelude::*; //} #[test] fn test_token () -> Result<(), Box> { - use crate::DslVal::*; + use crate::Val::*; let source = ":f00"; - let mut token = CstVal::new(source, 0, 1, Sym(":")); + let mut token = CstToken::new(source, 0, 1, Sym(":")); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 2, Sym(":f"))); + assert_eq!(token, CstToken::new(source, 0, 2, Sym(":f"))); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 3, Sym(":f0"))); + assert_eq!(token, CstToken::new(source, 0, 3, Sym(":f0"))); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 4, Sym(":f00"))); + assert_eq!(token, CstToken::new(source, 0, 4, Sym(":f00"))); assert_eq!(None, CstIter::new("").next()); assert_eq!(None, CstIter::new(" \n \r \t ").next()); diff --git a/output/src/ops.rs b/output/src/ops.rs index 9c528f2..13ade31 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -380,7 +380,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] }); -/// Enabling the `dsl` feature implements [DslFrom] for +/// Enabling the `dsl` feature implements [FromDsl] for /// the layout elements that are provided by this crate. #[cfg(feature = "dsl")] mod ops_dsl { use crate::*; @@ -391,7 +391,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ //($Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$($arg:ident $(:$ty:ty)?),*] $expr:expr) //)*) => { //$( - //impl DslFrom for $Struct$(<$($A),+>)? { + //impl FromDsl for $Struct$(<$($A),+>)? { //fn try_dsl_from ( //state: &S, dsl: &impl Dsl //) -> Perhaps { @@ -407,7 +407,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { - impl DslFrom for $Struct$(<$($A),+>)? { + impl FromDsl for $Struct$(<$($A),+>)? { fn try_dsl_from ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { @@ -422,7 +422,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { - impl DslFrom for $Struct$(<$($A),+>)? { + impl FromDsl for $Struct$(<$($A),+>)? { fn try_dsl_from ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 06b184e..132d867 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -84,7 +84,7 @@ impl ToTokens for CommandDef { let mut out = TokenStream2::new(); for (arg, _ty) in arm.args() { write_quote_to(&mut out, quote! { - #arg: DslFrom::dsl_from(self, words, ||"command error")?, + #arg: FromDsl::from_dsl(self, words, ||"command error")?, }); } out @@ -149,8 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl ::tengri::dsl::DslFrom<#state> for #command_enum { - fn try_dsl_from ( + impl ::tengri::dsl::FromDsl<#state> for #command_enum { + fn try_from_dsl ( state: &#state, value: &impl ::tengri::dsl::Dsl ) -> Perhaps { diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index d0f643b..ca5c9e7 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -57,7 +57,6 @@ impl ToTokens for ExposeDef { impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { let Self(block, exposed) = self; - let state = &block.self_ty; write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { self.expose_variants(out, t, variants); @@ -80,8 +79,8 @@ impl ExposeImpl { let arms = Self::with_predefined(t, quote! { #(#arms)* }); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::DslFrom<#state> for #t { - fn try_dsl_from (state: &#state, dsl: &impl Dsl) -> Perhaps { + impl ::tengri::dsl::FromDsl<#state> for #t { + fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { Ok(Some(match dsl.val() { #arms _ => { return Ok(None) } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 12012c3..0f377de 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -39,26 +39,22 @@ impl Parse for ViewImpl { impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; - let self_ty = &block.self_ty; - // Expressions are handled by built-in functions - // that operate over constants and symbols. - let builtin = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { - ::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( - #builtin::dsl_from(self, expr, ||Box::new("failed to load builtin".into()))? - .boxed() - )), - }); - // Symbols are handled by user-taked functions - // that take no parameters but `&self`. - let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { - ::tengri::dsl::DslVal::Sym(#key) => return Ok(Some( - self.#value().boxed() - )), - })); + let Self(_, ViewImpl { block, .. }) = self; + let generated = self.generated(); write_quote_to(out, quote! { - // Original user-taked implementation: #block + #generated + }) + } +} + +impl ViewDef { + fn generated (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let self_ty = &block.self_ty; + let builtins = self.builtins(); + let exposed = self.exposed(); + quote! { /// Generated by [tengri_proc]. /// /// Makes [#self_ty] able to construct the [Render]able @@ -70,11 +66,30 @@ impl ToTokens for ViewDef { fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { - use ::tengri::dsl::DslVal::*; - Ok(match dsl.val() { #(#builtin)* #(#exposed)* _ => return Ok(None) }) + Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) } } - }) + } + } + /// Expressions are handled by built-in functions + /// that operate over constants and symbols. + fn builtins (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { + ::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( + #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? + .boxed() + )), + }); + quote! { #(#builtins)* } + } + /// Symbols are handled by user-taked functions that take no parameters but `&self`. + fn exposed (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { + #key => return Ok(Some(self.#value().boxed())), + })); + quote! { ::tengri::dsl::DslVal::Sym(key) => match key.as_ref() { #(#exposed)* } } } }