diff --git a/Cargo.toml b/Cargo.toml index fa89dab..a7bb2eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ 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/] b/] new file mode 100644 index 0000000..93bdbbd --- /dev/null +++ b/] @@ -0,0 +1,200 @@ +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/Cargo.toml b/dsl/Cargo.toml index 256b599..ab0bc37 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -7,7 +7,6 @@ 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 new file mode 100644 index 0000000..ac85d87 --- /dev/null +++ b/dsl/src/ast.rs @@ -0,0 +1,38 @@ +//! 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 new file mode 100644 index 0000000..a993760 --- /dev/null +++ b/dsl/src/cst.rs @@ -0,0 +1,319 @@ +//! 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<'src>(pub CstIter<'src>); +impl<'src> Dsl for Cst<'src> { + type Str = &'src str; + type Exp = CstIter<'src>; + 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<'src> { + /// Meaning of token. + pub value: DslVal<&'src str, CstIter<'src>>, + /// Reference to source text. + pub source: &'src 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>; + fn nth (&self, index: usize) -> Option> { + todo!() + } +} + +impl<'src> CstVal<'src> { + pub const fn new ( + source: &'src str, + start: usize, + length: usize, + value: DslVal<&'src str, CstIter<'src>> + ) -> 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 { + self.slice_source(self.source) + } + pub const fn slice_source <'range> (&'src 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 { + str_range(source, self.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: DslVal<&'src str, CstIter<'src>>) -> Self { + Self { value, ..self } + } + pub const fn value (&self) -> DslVal<&'src str, CstIter<'src>> { + 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<'src>(pub CstConstIter<'src>); +impl<'src> Dsl for CstIter<'src> { + type Str = &'src 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)), + }) + } +} +impl<'src> CstIter<'src> { + pub const fn new (source: &'src str) -> Self { + Self(CstConstIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } +} +impl<'src> Iterator for CstIter<'src> { + type Item = CstVal<'src>; + 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> { + self.collect() + } +} +impl<'src> Into> for CstIter<'src> { + 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<'src>(pub &'src str); +impl<'src> Dsl for CstConstIter<'src> { + type Str = &'src 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, _)|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), + }) + } +} +impl<'src> CstConstIter<'src> { + pub const fn new (source: &'src 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)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } + pub const fn next_mut (&mut self) -> Option<(CstVal<'src>, Self)> { + match self.peek() { + Some(token) => Some((token, self.chomp(token.end()))), + None => None + } + } +} + +/// 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!(<'src>|self: CstConstIter<'src>| + => CstVal<'src> + => 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 <'src> (source: &'src str) -> Option> { + use DslVal::*; + let mut token: CstVal<'src> = 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 new file mode 100644 index 0000000..b2733e0 --- /dev/null +++ b/dsl/src/dsl.rs @@ -0,0 +1,178 @@ +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 trait Dsl { + 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)); + +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 3e26227..aa255fc 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,376 +1,19 @@ #![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 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; -pub(crate) use self::DslError::*; +pub(crate) use self::DslErr::*; + +mod dsl; pub use self::dsl::*; +mod ast; pub use self::ast::*; +mod cst; pub use self::cst::*; + #[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 dfb37f4..b275573 100644 --- a/dsl/src/test.rs +++ b/dsl/src/test.rs @@ -12,10 +12,10 @@ use proptest::prelude::*; } #[test] fn test_num () { - let _digit = Token::to_digit('0'); - let _digit = Token::to_digit('x'); - let _number = Token::to_number(&"123"); - let _number = Token::to_number(&"12asdf3"); + let _digit = to_digit('0'); + let _digit = to_digit('x'); + let _number = to_number(&"123"); + let _number = to_number(&"12asdf3"); } //proptest! { //#[test] fn proptest_source_iter ( @@ -48,15 +48,15 @@ use proptest::prelude::*; //} #[test] fn test_token () -> Result<(), Box> { - use crate::Val::*; + use crate::DslVal::*; let source = ":f00"; - let mut token = CstToken::new(source, 0, 1, Sym(":")); + let mut token = CstVal::new(source, 0, 1, Sym(":")); token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 2, Sym(":f"))); + assert_eq!(token, CstVal::new(source, 0, 2, Sym(":f"))); token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 3, Sym(":f0"))); + assert_eq!(token, CstVal::new(source, 0, 3, Sym(":f0"))); token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 4, Sym(":f00"))); + assert_eq!(token, CstVal::new(source, 0, 4, Sym(":f00"))); assert_eq!(None, CstIter::new("").next()); assert_eq!(None, CstIter::new(" \n \r \t ").next()); diff --git a/editor/Cargo.toml b/editor/Cargo.toml deleted file mode 100644 index 692cf03..0000000 --- a/editor/Cargo.toml +++ /dev/null @@ -1,5 +0,0 @@ -[package] -name = "tengri_editor" -description = "Embeddable editor for Tengri DSL." -version = { workspace = true } -edition = { workspace = true } diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 7ac0644..e642490 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,27 +1,11 @@ use crate::*; -/// A collection of input bindings. +/// 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)] 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, - description: Option>, - source: Option>, -} -impl InputMap { +#[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())? { @@ -34,38 +18,29 @@ impl InputMap { Self::from_dsl(CstIter::from(source.as_ref())) } /// Create input layer collection from DSL. - pub fn from_dsl (dsl: D) -> Usually { + pub fn from_dsl (dsl: impl Dsl) -> Usually { use DslVal::*; - let mut input_map: BTreeMap>> = Default::default(); - let mut index = 0; - while let Some(Exp(_, mut exp)) = dsl.nth(index) { + let mut input_map: Self = Self(Default::default()); + while let Exp(_, mut exp) = dsl.val() { 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(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(Sym(sym)) = expr.nth(0) => { + //input_map.unconditional.push(expr); + todo!("binding"); }, - Some(Key(key)) if key.as_ref() == "if" => { - todo!("conditional binding {exp:?} {key:?}"); + Some(Exp(_, expr)) if let Some(Key(key)) = expr.nth(0) && key.as_ref() == "if" => { + todo!("conditional binding"); }, _ => return Result::Err(format!("invalid token in keymap: {val:?}").into()), } - index += 1; } - Ok(Self(input_map)) + Ok(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 0779092..86c6150 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -2,8 +2,7 @@ #![feature(if_let_guard)] pub(crate) use std::fmt::Debug; -pub(crate) use std::sync::Arc; -pub(crate) use std::collections::{BTreeMap, HashMap}; +pub(crate) use std::collections::BTreeMap; pub(crate) use std::path::{Path, PathBuf}; pub(crate) use std::fs::exists; pub(crate) use tengri_core::*; diff --git a/output/src/ops.rs b/output/src/ops.rs index 13ade31..9c528f2 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 [FromDsl] for +/// Enabling the `dsl` feature implements [DslFrom] 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 FromDsl for $Struct$(<$($A),+>)? { + //impl DslFrom 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 FromDsl for $Struct$(<$($A),+>)? { + impl DslFrom 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 FromDsl for $Struct$(<$($A),+>)? { + impl DslFrom 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 132d867..06b184e 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: FromDsl::from_dsl(self, words, ||"command error")?, + #arg: DslFrom::dsl_from(self, words, ||"command error")?, }); } out @@ -149,8 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl ::tengri::dsl::FromDsl<#state> for #command_enum { - fn try_from_dsl ( + impl ::tengri::dsl::DslFrom<#state> for #command_enum { + fn try_dsl_from ( state: &#state, value: &impl ::tengri::dsl::Dsl ) -> Perhaps { diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index ca5c9e7..98b0df0 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -57,9 +57,40 @@ 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); + 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) })) + } + } + }); } if exposed.len() > 0 { //panic!("{}", quote! {#out}); @@ -67,66 +98,6 @@ 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::FromDsl<#state> for #t { - fn try_from_dsl (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 { diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 0f377de..12012c3 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -39,22 +39,26 @@ impl Parse for ViewImpl { impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(_, ViewImpl { block, .. }) = self; - let generated = self.generated(); - write_quote_to(out, quote! { - #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! { + 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() + )), + })); + write_quote_to(out, quote! { + // Original user-taked implementation: + #block /// Generated by [tengri_proc]. /// /// Makes [#self_ty] able to construct the [Render]able @@ -66,30 +70,11 @@ impl ViewDef { fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { - Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) + use ::tengri::dsl::DslVal::*; + Ok(match dsl.val() { #(#builtin)* #(#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)* } } + }) } } diff --git a/tengri/src/test.rs b/tengri/src/test.rs index c782996..025e522 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,15 +1,9 @@ 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<()> { - #[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 } + struct Test { keys: InputMap<(), Ast> } 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 9ee6be6..3bf7791 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 crossterm::event::Event, + pub Event, ); impl Input for TuiIn { - type Event = crossterm::event::Event; + type Event = Event; type Handled = bool; - fn event (&self) -> &crossterm::event::Event { &self.1 } + fn event (&self) -> &Event { &self.1 } fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } fn done (&self) { self.0.store(true, Relaxed); } } diff --git a/editor/src/main.rs b/vin/Cargo.toml similarity index 100% rename from editor/src/main.rs rename to vin/Cargo.toml