diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs new file mode 100644 index 0000000..7922d26 --- /dev/null +++ b/dsl/src/dsl_ast.rs @@ -0,0 +1,70 @@ +use crate::*; +use std::sync::Arc; +use std::fmt::{Debug, Display, Formatter}; + +/// Emits tokens. +pub trait Ast: Debug { + fn peek (&self) -> Option; + fn next (&mut self) -> Option; + fn rest (self) -> Option>; +} + +/// A [Cst] can be used as an [Ast]. +impl<'source: 'static> Ast for Cst<'source> { + fn peek (&self) -> Option { + Cst::peek(self).map(|token|token.value.into()) + } + fn next (&mut self) -> Option { + Iterator::next(self).map(|token|token.value.into()) + } + fn rest (self) -> Option> { + self.peek().is_some().then(||Box::new(self) as Box) + } +} + +#[derive(Clone, Default, Debug)] +pub enum AstValue { + #[default] Nil, + Err(DslError), + Num(usize), + Sym(Arc), + Key(Arc), + Str(Arc), + Exp(Arc>), +} + +impl std::fmt::Display for AstValue { + fn fmt (&self, out: &mut Formatter) -> Result<(), std::fmt::Error> { + use AstValue::*; + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(e) => format!("{e:?}"), + }) + } +} + +impl<'source: 'static> From> for AstValue { + fn from (other: CstValue<'source>) -> Self { + use CstValue::*; + match other { + Nil => Self::Nil, + Err(e) => Self::Err(e), + Num(u) => Self::Num(u), + Sym(s) => Self::Sym(s.into()), + Key(s) => Self::Key(s.into()), + Str(s) => Self::Str(s.into()), + Exp(_, s) => Self::Exp(Arc::new(s.into())), + } + } +} + +impl<'source: 'static> Into> for Cst<'source> { + fn into (self) -> Box { + Box::new(self) + } +} diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs new file mode 100644 index 0000000..a45e77a --- /dev/null +++ b/dsl/src/dsl_cst.rs @@ -0,0 +1,291 @@ +use crate::*; + +/// Provides a native [Iterator] API over [CstConstIter], +/// emitting [CstToken] items. +/// +/// [Cst::next] returns just the [CstToken] and mutates `self`, +/// instead of returning an updated version of the struct as [CstConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct Cst<'a>(pub CstConstIter<'a>); + +/// Owns a reference to the source text. +/// [CstConstIter::next] emits subsequent pairs of: +/// * a [CstToken] 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<'a>(pub &'a str); + +/// A CST token, with reference to the source slice. +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstToken<'source> { + pub source: &'source str, + pub start: usize, + pub length: usize, + pub value: CstValue<'source>, +} + +/// The meaning of a CST token. Strip the source from this to get an [AstValue]. +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum CstValue<'source> { + #[default] Nil, + Err(DslError), + Num(usize), + Sym(&'source str), + Key(&'source str), + Str(&'source str), + Exp(usize, Cst<'source>), +} + +impl<'a> Cst<'a> { + pub const fn new (source: &'a str) -> Self { + Self(CstConstIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } +} + +impl<'a> Iterator for Cst<'a> { + type Item = CstToken<'a>; + fn next (&mut self) -> Option> { + self.0.next().map(|(item, rest)|{self.0 = rest; item}) + } +} + +impl<'a> From<&'a str> for Cst<'a> { + fn from (source: &'a str) -> Self{ + Self(CstConstIter(source)) + } +} + +impl<'a> From> for Cst<'a> { + fn from (source: CstConstIter<'a>) -> Self{ + Self(source) + } +} + +/// Implement the const iterator pattern. +#[macro_export] 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!(<'a>|self: CstConstIter<'a>| => CstToken<'a> => self.next_mut().map(|(result, _)|result)); + +impl<'a> From<&'a str> for CstConstIter<'a> { + fn from (source: &'a str) -> Self{ + Self::new(source) + } +} + +impl<'a> CstConstIter<'a> { + pub const fn new (source: &'a 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<(CstToken<'a>, Self)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } + pub const fn next_mut (&mut self) -> Option<(CstToken<'a>, Self)> { + match self.peek() { + Some(token) => Some((token, self.chomp(token.end()))), + None => None + } + } +} + +/// Static iteration helper. +#[macro_export] 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 <'a> (source: &'a str) -> Option> { + use CstValue::*; + let mut token: CstToken<'a> = CstToken::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(), + '(' => + CstToken::new(source, start, 1, Exp(1, Cst::new(str_range(source, start, start + 1)))), + '"' => + CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), + ':'|'@' => + CstToken::new(source, start, 1, Sym(str_range(source, start, start + 1))), + '/'|'a'..='z' => + CstToken::new(source, start, 1, Key(str_range(source, start, start + 1))), + '0'..='9' => + CstToken::new(source, start, 1, match to_digit(c) { + Ok(c) => CstValue::Num(c), + Result::Err(e) => CstValue::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)) + }) +} + +impl<'source> CstToken<'source> { + pub const fn new ( + source: &'source str, start: usize, length: usize, value: CstValue<'source> + ) -> Self { + Self { source, start, length, value } + } + pub const fn end (&self) -> usize { + self.start.saturating_add(self.length) + } + pub const fn slice (&'source self) -> &'source str { + self.slice_source(self.source) + } + pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: CstValue<'source>) -> Self { + Self { value, ..self } + } + pub const fn value (&self) -> CstValue { + self.value + } + pub const fn error (self, error: DslError) -> Self { + Self { value: CstValue::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 { + use CstValue::*; + match to_digit(c) { + Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, + Result::Err(e) => Self { value: Err(e), ..self.grow() }, + } + } + pub const fn grow_key (self) -> Self { + use CstValue::*; + let token = self.grow(); + token.with_value(Key(token.slice_source(self.source))) + } + pub const fn grow_sym (self) -> Self { + use CstValue::*; + let token = self.grow(); + token.with_value(Sym(token.slice_source(self.source))) + } + pub const fn grow_str (self) -> Self { + use CstValue::*; + let token = self.grow(); + token.with_value(Str(token.slice_source(self.source))) + } + pub const fn grow_exp (self) -> Self { + use CstValue::*; + let token = self.grow(); + if let Exp(depth, _) = token.value { + token.with_value(Exp(depth, Cst::new(token.slice_source_exp(self.source)))) + } else { + unreachable!() + } + } + pub const fn grow_in (self) -> Self { + let token = self.grow_exp(); + if let CstValue::Exp(depth, source) = token.value { + token.with_value(CstValue::Exp(depth.saturating_add(1), source)) + } else { + unreachable!() + } + } + pub const fn grow_out (self) -> Self { + let token = self.grow_exp(); + if let CstValue::Exp(depth, source) = token.value { + if depth > 0 { + token.with_value(CstValue::Exp(depth - 1, source)) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + } +} + +impl<'source> std::fmt::Display for CstValue<'source> { + fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + use CstValue::*; + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(_, e) => format!("{e:?}"), + }) + } +} diff --git a/dsl/src/dsl_domain.rs b/dsl/src/dsl_domain.rs new file mode 100644 index 0000000..283d79a --- /dev/null +++ b/dsl/src/dsl_domain.rs @@ -0,0 +1,64 @@ +use crate::*; + +pub trait Eval { + fn eval (&self, input: Input) -> Perhaps; +} + +/// May construct [Self] from token stream. +pub trait Dsl: Sized { + type State; + fn try_provide (state: Self::State, source: impl Ast) -> Perhaps; + fn provide >, F: Fn()->E> ( + state: Self::State, source: impl Ast, error: F + ) -> Usually { + let next = source.peek().clone(); + if let Some(value) = Self::try_provide(state, source)? { + Ok(value) + } else { + Result::Err(format!("dsl: {}: {next:?}", error().into()).into()) + } + } +} + +//pub trait Give<'state, 'source, Type> { + ///// Implement this to be able to [Give] [Type] from the [Cst]. + ///// Advance the stream if returning `Ok>`. + //fn give (&'state self, words: Cst<'source>) -> Perhaps; + ///// Return custom error on [None]. + //fn give_or_fail >, F: Fn()->E> ( + //&'state self, mut words: Cst<'source>, error: F + //) -> Usually { + //let next = words.peek().map(|x|x.value).clone(); + //if let Some(value) = Give::::give(self, words)? { + //Ok(value) + //} else { + //Result::Err(format!("give: {}: {next:?}", error().into()).into()) + //} + //} +//} +//#[macro_export] macro_rules! give { + //($Type:ty|$state:ident:$State:ident,$words:ident|$expr:expr) => { + //impl Give<$Type> for $State { + //fn give (&self, mut $words: Cst) -> Perhaps<$Type> { + //let $state = self; + //$expr + //} + //} + //}; + //($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { + //impl)++ $(, $Arg)*> Give<$Type> for State { + //fn give (&self, mut $words: Cst) -> Perhaps<$Type> { + //let $state = self; + //$expr + //} + //} + //} +//} +/////// Implement the [Give] trait, which boils down to +/////// specifying two types and providing an expression. +//#[macro_export] macro_rules! from_dsl { + //($Type:ty: |$state:ident:$State:ty, $words:ident|$expr:expr) => { + //give! { $Type|$state:$State,$words|$expr } + //}; +//} + diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs deleted file mode 100644 index 63bc567..0000000 --- a/dsl/src/dsl_parse.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::*; - -/// Implement the const iterator pattern. -#[macro_export] 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; - } - } -} - -/// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter] -/// [TokenIter::next] returns just the [Token] and mutates `self`, -/// instead of returning an updated version of the struct as [SourceIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct TokenIter<'a>( - pub SourceIter<'a> -); - -impl<'a> TokenIter<'a> { - pub const fn new (source: &'a str) -> Self { - Self(SourceIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} - -impl<'a> Iterator for TokenIter<'a> { - type Item = Token<'a>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{self.0 = rest; item}) - } -} - -impl<'a> From<&'a str> for TokenIter<'a> { - fn from (source: &'a str) -> Self{ - Self(SourceIter(source)) - } -} - -impl<'a> From> for TokenIter<'a> { - fn from (source: SourceIter<'a>) -> Self{ - Self(source) - } -} - -/// Owns a reference to the source text. -/// [SourceIter::next] emits subsequent pairs of: -/// * a [Token] and -/// * the source text remaining -/// * [ ] TODO: maybe [SourceIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceIter<'a>(pub &'a str); - -const_iter!(<'a>|self: SourceIter<'a>| => Token<'a> => self.next_mut().map(|(result, _)|result)); - -impl<'a> From<&'a str> for SourceIter<'a> { - fn from (source: &'a str) -> Self{ - Self::new(source) - } -} - -impl<'a> SourceIter<'a> { - pub const fn new (source: &'a 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<(Token<'a>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(Token<'a>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} - -/// Static iteration helper. -#[macro_export] 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 <'a> (source: &'a str) -> Option> { - let mut token: Token<'a> = Token::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(), - '(' => - Token::new(source, start, 1, Exp(1, TokenIter::new(str_range(source, start, start + 1)))), - '"' => - Token::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - Token::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - Token::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - Token::new(source, start, 1, match to_digit(c) { - Ok(c) => Value::Num(c), - Result::Err(e) => Value::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_provide.rs b/dsl/src/dsl_provide.rs deleted file mode 100644 index 34dabd6..0000000 --- a/dsl/src/dsl_provide.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::*; - -///// Implement the [Give] trait, which boils down to -///// specifying two types and providing an expression. -#[macro_export] macro_rules! from_dsl { - ($Type:ty: |$state:ident:$State:ty, $words:ident|$expr:expr) => { - take! { $Type|$state:$State,$words|$expr } - }; -} - -/// [Take]s instances of [Type] given [TokenIter]. -pub trait Give<'state, Type> { - /// Implement this to be able to [Give] [Type] from the [TokenIter]. - /// Advance the stream if returning `Ok>`. - fn give <'source: 'state> (&self, words: TokenIter<'source>) -> Perhaps; - /// Return custom error on [None]. - fn give_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( - &self, mut words: TokenIter<'source>, error: F - ) -> Usually { - let next = words.peek().map(|x|x.value).clone(); - if let Some(value) = Give::::give(self, words)? { - Ok(value) - } else { - Result::Err(format!("give: {}: {next:?}", error().into()).into()) - } - } -} -#[macro_export] macro_rules! give { - () => { - impl<'state, Type: Take<'state, State>, State> Give<'state, Type> for State { - fn give <'source: 'state> (&self, mut words:TokenIter<'source>) -> Perhaps { - Type::take(self, words) - } - } - }; - (box) => { - //impl<'state, T, Type: Take<'state, Box>> Give<'state, Box> for Box { - //fn give (&self, mut words:TokenIter<'source>) -> Perhaps> { - //Type::take(self, words) - //} - //} - }; - ($Type:ty: $State:ty) => { - impl<'state, $Type: Take<'state, $State>> Give<'state, $Type> for $State { - fn give <'source: 'state> (&self, mut words:TokenIter<'source>) -> Perhaps<$Type> { - $Type::take(self, words) - } - } - }; - ($Type:ty|$state:ident:$State:ident,$words:ident|$expr:expr) => { - impl<'state> Give<'state, $Type> for $State { - fn give <'source: 'state> (&self, mut $words:TokenIter<'source>) -> Perhaps<$Type> { - let $state = self; - $expr - } - } - }; - ($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { - impl<'state, State: $(Give<'state, $Arg> +)* 'state $(, $Arg)*> Give<'state, $Type> for State { - fn give <'source: 'state> (&self, mut $words:TokenIter<'source>) -> Perhaps<$Type> { - let $state = self; - $expr - } - } - } -} -/// [Give]s instances of [Self] given [TokenIter]. -pub trait Take<'state, State>: Sized { - /// Implement this to be able to [Take] [Self] from the [TokenIter]. - /// Advance the stream if returning `Ok>`. - fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps; - /// Return custom error on [None]. - fn take_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( - state: &State, mut words:TokenIter<'source>, error: F - ) -> Usually { - let next = words.peek().map(|x|x.value).clone(); - if let Some(value) = Take::::take(state, words)? { - Ok(value) - } else { - Result::Err(format!("take: {}: {next:?}", error().into()).into()) - } - } -} -#[macro_export] macro_rules! take { - () => { - impl<'state, Type: 'state, State: Give<'state, Type> + 'state> Take<'state, State> for Type { - fn take <'source: 'state> (state: &State, mut words:TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - (box) => { - impl<'state, T, State: Give<'state, Box> + 'state> Take<'state, State> for Box { - fn take <'source: 'state> (state: &State, mut words:TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - ($Type:ty:$State:ty) => { - impl<'state> Take<'state, $State> for $Type { - fn take <'source: 'state> (state: &$State, mut words:TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - ($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { - impl<'state, - State: Give<'state, bool> + $(Give<'state, $Arg> + )* 'state - $(, $Arg: Take<'state, State> + 'state)* - > Take<'state, State> for $Type { - fn take <'source: 'state> ($state: &State, mut $words:TokenIter<'source>) -> Perhaps { - $expr - } - } - }; - ($Type:path$(,$Arg:ident)*|$state:ident:$State:path,$words:ident|$expr:expr) => { - impl<'state $(, $Arg: 'state)*> Take<'state, $State> for $Type { - fn take <'source: 'state> ($state: &$State, mut $words:TokenIter<'source>) -> Perhaps { - $expr - } - } - }; -} - -// auto impl graveyard: - -//impl<'state, State: Give, Type: 'state> Take<'state, State> for Type { - //fn take <'state> (state: &State, mut words:TokenIter<'source>) - //-> Perhaps - //{ - //state.take(words) - //} -//} - -//#[cfg(feature="dsl")] -//impl<'state, E: 'state, State: Give<'state, Box + 'state>>> -//Take<'state, State> for Box + 'state> { - //fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { - //state.give(words) - //} -//} - -impl<'state, Type: Take<'state, State>, State> Give<'state, Type> for State { - fn give <'source: 'state> (&self, words: TokenIter<'source>) -> Perhaps { - Type::take(self, words) - } -} diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs deleted file mode 100644 index d45203b..0000000 --- a/dsl/src/dsl_token.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::*; - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> { - pub source: &'source str, - pub start: usize, - pub length: usize, - pub value: Value<'source>, -} - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { - #[default] Nil, - Err(DslError), - Num(usize), - Sym(&'source str), - Key(&'source str), - Str(&'source str), - Exp(usize, TokenIter<'source>), -} - -impl<'source> std::fmt::Display for Value<'source> { - fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(out, "{}", match self { - Nil => String::new(), - Err(e) => format!("[error: {e}]"), - Num(n) => format!("{n}"), - Sym(s) => format!("{s}"), - Key(s) => format!("{s}"), - Str(s) => format!("{s}"), - Exp(_, e) => format!("{e:?}"), - }) - } -} - -impl<'source> Token<'source> { - pub const fn new ( - source: &'source str, start: usize, length: usize, value: Value<'source> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'source self) -> &'source str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: Value<'source>) -> Self { - Self { value, ..self } - } - pub const fn value (&self) -> Value { - self.value - } - pub const fn error (self, error: DslError) -> Self { - Self { value: Value::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) { - Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: Err(e), ..self.grow() }, - } - } - pub const fn grow_key (self) -> Self { - let token = self.grow(); - token.with_value(Key(token.slice_source(self.source))) - } - pub const fn grow_sym (self) -> Self { - let token = self.grow(); - token.with_value(Sym(token.slice_source(self.source))) - } - pub const fn grow_str (self) -> Self { - let token = self.grow(); - token.with_value(Str(token.slice_source(self.source))) - } - pub const fn grow_exp (self) -> Self { - let token = self.grow(); - if let Exp(depth, _) = token.value { - token.with_value(Exp(depth, TokenIter::new(token.slice_source_exp(self.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - token.with_value(Value::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - if depth > 0 { - token.with_value(Value::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 712eced..0cd66c8 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -42,13 +42,12 @@ pub(crate) use std::fmt::Debug; 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::Value::*; pub(crate) use self::DslError::*; +mod dsl_ast; pub use self::dsl_ast::*; +mod dsl_cst; pub use self::dsl_cst::*; +mod dsl_domain; pub use self::dsl_domain::*; mod dsl_error; pub use self::dsl_error::*; -mod dsl_token; pub use self::dsl_token::*; -mod dsl_parse; pub use self::dsl_parse::*; -mod dsl_provide; pub use self::dsl_provide::*; #[cfg(test)] mod test_token_iter { use crate::*; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 9c08545..3610f36 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,139 +1,67 @@ use crate::*; use std::marker::PhantomData; - -/// [Input] state that can be matched against a [Value]. -pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } - -/// A pre-configured mapping of input events to commands. -pub trait KeyMap<'k, S, C: Take<'k, S> + Command, I: DslInput> { - /// Try to find a command that matches the current input event. - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; +/// List of input layers with optional conditional filters. +#[derive(Default, Debug)] pub struct InputLayers(Vec>); +#[derive(Default, Debug)] pub struct InputLayer{ + __: PhantomData, + condition: Option>, + bindings: Box, } - -/// A [SourceIter] can be a [KeyMap]. -impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for SourceIter<'k> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { - let mut iter = self.clone(); - while let Some((token, rest)) = iter.next() { - iter = rest; - match token { - Token { value: Value::Exp(0, exp_iter), .. } => { - let mut exp_iter = exp_iter.clone(); - match exp_iter.next() { - Some(Token { value: Value::Sym(binding), .. }) => { - if input.matches_dsl(binding) { - if let Some(command) = Take::take(state, exp_iter)? { - return Ok(Some(command)) - } - } - }, - _ => panic!("invalid config (expected symbol)") - } - }, - _ => panic!("invalid config (expected expression)") - } - } - Ok(None) - } -} - -/// A [TokenIter] can be a [KeyMap]. -impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for TokenIter<'k> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { - let mut iter = self.clone(); - while let Some(next) = iter.next() { - match next { - Token { value: Value::Exp(0, exp_iter), .. } => { - let mut e = exp_iter.clone(); - match e.next() { - Some(Token { value: Value::Sym(binding), .. }) => { - if input.matches_dsl(binding) { - if let Some(command) = Take::take(state, e)? { - return Ok(Some(command)) - } - } - }, - _ => panic!("invalid config (expected symbol, got: {exp_iter:?})") - } - }, - _ => panic!("invalid config (expected expression, got: {next:?})") - } - } - Ok(None) - } -} - -pub type InputCondition<'k, S> = BoxUsually + Send + Sync + 'k>; - -/// A collection of pre-configured mappings of input events to commands, -/// which may be made available subject to given conditions. -pub struct InputMap<'k, - S, - C: Command + Take<'k, S>, - I: DslInput, - M: KeyMap<'k, S, C, I> -> { - __: PhantomData<&'k (S, C, I)>, - pub layers: Vec<(InputCondition<'k, S>, M)>, -} - -impl<'k, - S, - C: Command + Take<'k, S>, - I: DslInput, - M: KeyMap<'k, S, C, I> -> Default for InputMap<'k, S, C, I, M>{ - fn default () -> Self { - Self { __: PhantomData, layers: vec![] } - } -} - -impl<'k, S, C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> -InputMap<'k, S, C, I, M> { - pub fn new (keymap: M) -> Self { - Self::default().layer(keymap) - } - pub fn layer (mut self, keymap: M) -> Self { - self.add_layer(keymap); - self - } - pub fn add_layer (&mut self, keymap: M) -> &mut Self { - self.add_layer_if(Box::new(|_|Ok(true)), keymap); - self - } - pub fn layer_if (mut self, condition: InputCondition<'k, S>, keymap: M) -> Self { - self.add_layer_if(condition, keymap); - self - } - pub fn add_layer_if (&mut self, condition: InputCondition<'k, S>, keymap: M) -> &mut Self { - self.layers.push((Box::new(condition), keymap)); +impl InputLayers { + pub fn new (layer: Box) -> Self + { Self(vec![]).layer(layer) } + pub fn layer (mut self, layer: Box) -> Self + { self.add_layer(layer); self } + pub fn layer_if (mut self, condition: Box, layer: Box) -> Self + { self.add_layer_if(Some(condition), layer); self } + pub fn add_layer (&mut self, layer: Box) -> &mut Self + { self.add_layer_if(None, layer.into()); self } + pub fn add_layer_if ( + &mut self, + condition: Option>, + bindings: Box + ) -> &mut Self { + self.0.push(InputLayer { condition, bindings, __: Default::default() }); self } } - -impl<'k, S, C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> -std::fmt::Debug for InputMap<'k, S, C, I, M> { - fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(f, "[InputMap: {} layer(s)]", self.layers.len()) - } -} - -/// An [InputMap] can be a [KeyMap]. -impl<'k, - S, - C: Command + Take<'k, S>, - I: DslInput, - M: KeyMap<'k, S, C, I> -> KeyMap<'k, S, C, I> for InputMap<'k, S, C, I, M> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { - for (condition, keymap) in self.layers.iter() { - if !condition(state)? { - continue - } - if let Some(command) = keymap.keybind_resolve(state, input)? { +impl, I: Input, O: Command> Eval for InputLayers { + fn eval (&self, input: I) -> Perhaps { + for layer in self.0.iter() { + if let Some(command) = Ok(if let Some(condition) = layer.condition.as_ref() { + if self.matches(condition)? { + self.state().eval(*self)? + } else { + None + } + } else { + self.state().eval(*self)? + })? { return Ok(Some(command)) } } + } +} +/// [Input] state that can be matched against a [CstValue]. +pub trait InputState: Input { + fn state (&self) -> &S; + fn matches (&self, condition: &Box) -> Usually; +} +impl, I: InputState, O: Command> Eval +for InputLayer { + fn eval (&self, input: I) -> Perhaps { + if let Some(AstToken::Exp(exp)) = iter.peek() { + let mut e = exp.clone(); + if let Some(AstToken::Sym(binding)) = e.next() + && input.matches_dsl(binding) + && let Some(command) = Dsl::from_dsl(input.state(), e)? { + return Ok(Some(command)) + } else { + unreachable!("InputLayer: expected symbol, got: {e:?}") + } + } else { + panic!("InputLayer: expected expression, got: {self:?}") + } Ok(None) } } diff --git a/output/src/lib.rs b/output/src/lib.rs index 394f332..30e8fff 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -3,8 +3,16 @@ #![feature(impl_trait_in_assoc_type)] pub(crate) use tengri_core::*; pub(crate) use std::marker::PhantomData; -#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; -mod space; pub use self::space::*; -mod ops; pub use self::ops::*; -mod output; pub use self::output::*; + +#[cfg(feature = "dsl")] +pub(crate) use ::tengri_dsl::*; + +mod space; pub use self::space::*; + +mod ops; pub use self::ops::*; + +#[cfg(feature = "dsl")] mod ops_dsl; + +mod output; pub use self::output::*; + #[cfg(test)] mod test; diff --git a/output/src/ops.rs b/output/src/ops.rs index 7b8010e..cfeb23e 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -1,10 +1,382 @@ -//mod reduce; pub use self::reduce::*; -mod align; pub use self::align::*; -mod bsp; pub use self::bsp::*; -mod either; pub use self::either::*; +//! Transform: +//! ``` +//! use ::tengri::{output::*, tui::*}; +//! let area: [u16;4] = [10, 10, 20, 20]; +//! fn test (area: [u16;4], item: &impl Content, expected: [u16;4]) { +//! assert_eq!(Content::layout(item, area), expected); +//! assert_eq!(Render::layout(item, area), expected); +//! }; +//! test(area, &(), [20, 20, 0, 0]); +//! +//! test(area, &Fill::xy(()), area); +//! test(area, &Fill::x(()), [10, 20, 20, 0]); +//! test(area, &Fill::y(()), [20, 10, 0, 20]); +//! +//! //FIXME:test(area, &Fixed::x(4, ()), [18, 20, 4, 0]); +//! //FIXME:test(area, &Fixed::y(4, ()), [20, 18, 0, 4]); +//! //FIXME:test(area, &Fixed::xy(4, 4, unit), [18, 18, 4, 4]); +//! ``` +//! Align: +//! ``` +//! use ::tengri::{output::*, tui::*}; +//! let area: [u16;4] = [10, 10, 20, 20]; +//! fn test (area: [u16;4], item: &impl Content, expected: [u16;4]) { +//! assert_eq!(Content::layout(item, area), expected); +//! assert_eq!(Render::layout(item, area), expected); +//! }; +//! +//! let four = ||Fixed::xy(4, 4, ""); +//! test(area, &Align::nw(four()), [10, 10, 4, 4]); +//! test(area, &Align::n(four()), [18, 10, 4, 4]); +//! test(area, &Align::ne(four()), [26, 10, 4, 4]); +//! test(area, &Align::e(four()), [26, 18, 4, 4]); +//! test(area, &Align::se(four()), [26, 26, 4, 4]); +//! test(area, &Align::s(four()), [18, 26, 4, 4]); +//! test(area, &Align::sw(four()), [10, 26, 4, 4]); +//! test(area, &Align::w(four()), [10, 18, 4, 4]); +//! +//! let two_by_four = ||Fixed::xy(4, 2, ""); +//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]); +//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]); +//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]); +//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]); +//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]); +//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]); +//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); +//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); +//! ``` + +use crate::*; +use Direction::*; + mod map; pub use self::map::*; mod memo; pub use self::memo::*; mod stack; pub use self::stack::*; mod thunk; pub use self::thunk::*; mod transform; pub use self::transform::*; -mod when; pub use self::when::*; + +/// Renders multiple things on top of each other, +#[macro_export] macro_rules! lay { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} +} + +/// Stack southward. +#[macro_export] macro_rules! col { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}; +} + +/// Stack northward. +#[macro_export] macro_rules! col_up { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }} +} + +/// Stack eastward. +#[macro_export] macro_rules! row { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; +} + +/// Show an item only when a condition is true. +pub struct When(pub bool, pub A); +impl When { + /// Create a binary condition. + pub const fn new (c: bool, a: A) -> Self { Self(c, a) } +} +impl> Content for When { + fn layout (&self, to: E::Area) -> E::Area { + let Self(cond, item) = self; + let mut area = E::Area::zero(); + if *cond { + let item_area = item.layout(to); + area[0] = item_area.x(); + area[1] = item_area.y(); + area[2] = item_area.w(); + area[3] = item_area.h(); + } + area.into() + } + fn render (&self, to: &mut E) { + let Self(cond, item) = self; + if *cond { item.render(to) } + } +} + +/// Show one item if a condition is true and another if the condition is false +pub struct Either(pub bool, pub A, pub B); +impl Either { + /// Create a ternary view condition. + pub const fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) } +} +impl, B: Render> Content for Either { + fn layout (&self, to: E::Area) -> E::Area { + let Self(cond, a, b) = self; + if *cond { a.layout(to) } else { b.layout(to) } + } + fn render (&self, to: &mut E) { + let Self(cond, a, b) = self; + if *cond { a.render(to) } else { b.render(to) } + } +} + +/// 9th of area to place. +#[derive(Debug, Copy, Clone, Default)] +pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } + +pub struct Align(Alignment, A); + +impl Align { + #[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) } + #[inline] pub const fn x (a: A) -> Self { Self(Alignment::X, a) } + #[inline] pub const fn y (a: A) -> Self { Self(Alignment::Y, a) } + #[inline] pub const fn n (a: A) -> Self { Self(Alignment::N, a) } + #[inline] pub const fn s (a: A) -> Self { Self(Alignment::S, a) } + #[inline] pub const fn e (a: A) -> Self { Self(Alignment::E, a) } + #[inline] pub const fn w (a: A) -> Self { Self(Alignment::W, a) } + #[inline] pub const fn nw (a: A) -> Self { Self(Alignment::NW, a) } + #[inline] pub const fn sw (a: A) -> Self { Self(Alignment::SW, a) } + #[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) } + #[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) } +} + +impl> Content for Align { + fn content (&self) -> impl Render + '_ { + &self.1 + } + fn layout (&self, on: E::Area) -> E::Area { + use Alignment::*; + let it = Render::layout(&self.content(), on).xywh(); + let cx = on.x()+(on.w().minus(it.w())/2.into()); + let cy = on.y()+(on.h().minus(it.h())/2.into()); + let fx = (on.x()+on.w()).minus(it.w()); + let fy = (on.y()+on.h()).minus(it.h()); + let [x, y] = match self.0 { + Center => [cx, cy], + X => [cx, it.y()], + Y => [it.x(), cy], + NW => [on.x(), on.y()], + N => [cx, on.y()], + NE => [fx, on.y()], + W => [on.x(), cy], + E => [fx, cy], + SW => [on.x(), fy], + S => [cx, fy], + SE => [fx, fy], + }.into(); + [x, y, it.w(), it.h()].into() + } + fn render (&self, to: &mut E) { + to.place(Content::layout(self, to.area()), &self.content()) + } +} + +/// A split or layer. +pub struct Bsp( + pub(crate) Direction, + pub(crate) A, + pub(crate) B, +); +impl Bsp { + #[inline] pub const fn n (a: A, b: B) -> Self { Self(North, a, b) } + #[inline] pub const fn s (a: A, b: B) -> Self { Self(South, a, b) } + #[inline] pub const fn e (a: A, b: B) -> Self { Self(East, a, b) } + #[inline] pub const fn w (a: A, b: B) -> Self { Self(West, a, b) } + #[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) } + #[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) } +} +impl, B: Content> Content for Bsp { + fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); c } + fn render (&self, to: &mut E) { + let [area_a, area_b, _] = self.areas(to.area()); + let (a, b) = self.contents(); + match self.0 { + Below => { to.place(area_a, a); to.place(area_b, b); }, + _ => { to.place(area_b, b); to.place(area_a, a); } + } + } +} +impl, B: Content> BspAreas for Bsp { + fn direction (&self) -> Direction { self.0 } + fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } +} +pub trait BspAreas, B: Content> { + fn direction (&self) -> Direction; + fn contents (&self) -> (&A, &B); + fn areas (&self, outer: E::Area) -> [E::Area;3] { + let direction = self.direction(); + let [x, y, w, h] = outer.xywh(); + let (a, b) = self.contents(); + let [aw, ah] = a.layout(outer).wh(); + let [bw, bh] = b.layout(match direction { + Above | Below => outer, + South => [x, y + ah, w, h.minus(ah)].into(), + North => [x, y, w, h.minus(ah)].into(), + East => [x + aw, y, w.minus(aw), h].into(), + West => [x, y, w.minus(aw), h].into(), + }).wh(); + match direction { + Above | Below => { + let [x, y, w, h] = outer.center_xy([aw.max(bw), ah.max(bh)]); + let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah]; + let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + South => { + let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); + let a = [(x + w/2.into()).minus(aw/2.into()), y, aw, ah]; + let b = [(x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + North => { + let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); + let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah]; + let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + East => { + let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); + let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah]; + let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + West => { + let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); + let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah]; + let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + } + } +} + +/// Defines an enum that transforms its content +/// along either the X axis, the Y axis, or both. +macro_rules! transform_xy { + ($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$area:expr) => { + pub enum $Enum { X(A), Y(A), XY(A) } + impl $Enum { + #[inline] pub const fn x (item: A) -> Self { Self::X(item) } + #[inline] pub const fn y (item: A) -> Self { Self::Y(item) } + #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } + } + impl> Content for $Enum { + fn content (&self) -> impl Render + '_ { + match self { + Self::X(item) => item, + Self::Y(item) => item, + Self::XY(item) => item, + } + } + fn layout (&$self, $to: ::Area) -> ::Area { + use $Enum::*; + $area + } + } + } +} + +/// Defines an enum that parametrically transforms its content +/// along either the X axis, the Y axis, or both. +macro_rules! transform_xy_unit { + ($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$layout:expr) => { + pub enum $Enum { X(U, A), Y(U, A), XY(U, U, A), } + impl $Enum { + #[inline] pub const fn x (x: U, item: A) -> Self { Self::X(x, item) } + #[inline] pub const fn y (y: U, item: A) -> Self { Self::Y(y, item) } + #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } + } + impl> Content for $Enum { + fn layout (&$self, $to: E::Area) -> E::Area { + $layout.into() + } + fn content (&self) -> impl Render + '_ { + use $Enum::*; + Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }) + } + } + impl $Enum { + #[inline] pub fn dx (&self) -> U { + use $Enum::*; + match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, } + } + #[inline] pub fn dy (&self) -> U { + use $Enum::*; + match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, } + } + } + } +} + +transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{ + let [x0, y0, wmax, hmax] = to.xywh(); + let [x, y, w, h] = self.content().layout(to).xywh(); + match self { + X(_) => [x0, y, wmax, h], + Y(_) => [x, y0, w, hmax], + XY(_) => [x0, y0, wmax, hmax], + }.into() +}); + +transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ + let [x, y, w, h] = area.xywh(); + let fixed_area = match self { + Self::X(fw, _) => [x, y, *fw, h], + Self::Y(fh, _) => [x, y, w, *fh], + Self::XY(fw, fh, _) => [x, y, *fw, *fh], + }; + let [x, y, w, h] = Render::layout(&self.content(), fixed_area.into()).xywh(); + let fixed_area = match self { + Self::X(fw, _) => [x, y, *fw, h], + Self::Y(fh, _) => [x, y, w, *fh], + Self::XY(fw, fh, _) => [x, y, *fw, *fh], + }; + fixed_area +}); + +transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{ + let area = Render::layout(&self.content(), area); + match self { + Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()], + Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)], + Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)], + } +}); + +transform_xy_unit!("max/x" "max/y" "max/xy"|self: Max, area|{ + let [x, y, w, h] = area.xywh(); + Render::layout(&self.content(), match self { + Self::X(fw, _) => [x, y, *fw, h], + Self::Y(fh, _) => [x, y, w, *fh], + Self::XY(fw, fh, _) => [x, y, *fw, *fh], + }.into()) +}); + +transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render::layout( + &self.content(), + [area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into())); + +transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout( + &self.content(), + [area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into())); + +transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{ + let area = Render::layout(&self.content(), area); + [area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()] +}); + +transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{ + let area = Render::layout(&self.content(), area); + [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] +}); + +transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{ + let area = Render::layout(&self.content(), area); + let dx = self.dx(); + let dy = self.dy(); + [area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))] +}); + +transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ + let area = Render::layout(&self.content(), area); + let dx = self.dx(); + let dy = self.dy(); + [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] +}); diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs deleted file mode 100644 index ffd5b48..0000000 --- a/output/src/ops/align.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Aligns things to the container. Comes with caveats. -//! ``` -//! use ::tengri::{output::*, tui::*}; -//! let area: [u16;4] = [10, 10, 20, 20]; -//! fn test (area: [u16;4], item: &impl Content, expected: [u16;4]) { -//! assert_eq!(Content::layout(item, area), expected); -//! assert_eq!(Render::layout(item, area), expected); -//! }; -//! -//! let four = ||Fixed::xy(4, 4, ""); -//! test(area, &Align::nw(four()), [10, 10, 4, 4]); -//! test(area, &Align::n(four()), [18, 10, 4, 4]); -//! test(area, &Align::ne(four()), [26, 10, 4, 4]); -//! test(area, &Align::e(four()), [26, 18, 4, 4]); -//! test(area, &Align::se(four()), [26, 26, 4, 4]); -//! test(area, &Align::s(four()), [18, 26, 4, 4]); -//! test(area, &Align::sw(four()), [10, 26, 4, 4]); -//! test(area, &Align::w(four()), [10, 18, 4, 4]); -//! -//! let two_by_four = ||Fixed::xy(4, 2, ""); -//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]); -//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]); -//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]); -//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]); -//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]); -//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]); -//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); -//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); -//! ``` -use crate::*; -#[derive(Debug, Copy, Clone, Default)] -pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } -pub struct Align(Alignment, A); -#[cfg(feature = "dsl")] -impl<'state, State: Give<'state, A>, A: 'state> Take<'state, State> for Align { - fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { - todo!() - } -} -//give!(Align, A|state, words|Ok(Some(match words.peek() { - //Some(Token { value: Value::Key(key), .. }) => match key { - //"align/c"|"align/x"|"align/y"| - //"align/n"|"align/s"|"align/e"|"al;qign/w"| - //"align/nw"|"align/sw"|"align/ne"|"align/se" => { - //let _ = words.next().unwrap(); - //let content = Take::take_or_fail(state, &mut words.clone(), ||"expected content")?; - //match key { - //"align/c" => Self::c(content), - //"align/x" => Self::x(content), - //"align/y" => Self::y(content), - //"align/n" => Self::n(content), - //"align/s" => Self::s(content), - //"align/e" => Self::e(content), - //"align/w" => Self::w(content), - //"align/nw" => Self::nw(content), - //"align/ne" => Self::ne(content), - //"align/sw" => Self::sw(content), - //"align/se" => Self::se(content), - //_ => unreachable!() - //} - //}, - //_ => return Ok(None) - //}, - //_ => return Ok(None) -//}))); - -impl Align { - #[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) } - #[inline] pub const fn x (a: A) -> Self { Self(Alignment::X, a) } - #[inline] pub const fn y (a: A) -> Self { Self(Alignment::Y, a) } - #[inline] pub const fn n (a: A) -> Self { Self(Alignment::N, a) } - #[inline] pub const fn s (a: A) -> Self { Self(Alignment::S, a) } - #[inline] pub const fn e (a: A) -> Self { Self(Alignment::E, a) } - #[inline] pub const fn w (a: A) -> Self { Self(Alignment::W, a) } - #[inline] pub const fn nw (a: A) -> Self { Self(Alignment::NW, a) } - #[inline] pub const fn sw (a: A) -> Self { Self(Alignment::SW, a) } - #[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) } - #[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) } -} - -impl> Content for Align { - fn content (&self) -> impl Render + '_ { - &self.1 - } - fn layout (&self, on: E::Area) -> E::Area { - use Alignment::*; - let it = Render::layout(&self.content(), on).xywh(); - let cx = on.x()+(on.w().minus(it.w())/2.into()); - let cy = on.y()+(on.h().minus(it.h())/2.into()); - let fx = (on.x()+on.w()).minus(it.w()); - let fy = (on.y()+on.h()).minus(it.h()); - let [x, y] = match self.0 { - Center => [cx, cy], - X => [cx, it.y()], - Y => [it.x(), cy], - NW => [on.x(), on.y()], - N => [cx, on.y()], - NE => [fx, on.y()], - W => [on.x(), cy], - E => [fx, cy], - SW => [on.x(), fy], - S => [cx, fy], - SE => [fx, fy], - }.into(); - [x, y, it.w(), it.h()].into() - } - fn render (&self, to: &mut E) { - to.place(Content::layout(self, to.area()), &self.content()) - } -} diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs deleted file mode 100644 index e714f8e..0000000 --- a/output/src/ops/bsp.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::*; -pub use Direction::*; -/// A split or layer. -pub struct Bsp( - pub(crate) Direction, - pub(crate) A, - pub(crate) B, -); -impl, B: Content> Content for Bsp { - fn layout (&self, outer: E::Area) -> E::Area { - let [_, _, c] = self.areas(outer); - c - } - fn render (&self, to: &mut E) { - let [area_a, area_b, _] = self.areas(to.area()); - let (a, b) = self.contents(); - match self.0 { - Below => { to.place(area_a, a); to.place(area_b, b); }, - _ => { to.place(area_b, b); to.place(area_a, a); } - } - } -} -#[cfg(feature = "dsl")] take!(Bsp, A, B|state, words|Ok(if let Some(Token { - value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), - .. -}) = words.peek() { - if let Value::Key(key) = words.next().unwrap().value() { - let base = words.clone(); - let a: A = state.give_or_fail(words, ||"bsp: expected content 1")?; - let b: B = state.give_or_fail(words, ||"bsp: expected content 2")?; - return Ok(Some(match key { - "bsp/n" => Self::n(a, b), - "bsp/s" => Self::s(a, b), - "bsp/e" => Self::e(a, b), - "bsp/w" => Self::w(a, b), - "bsp/a" => Self::a(a, b), - "bsp/b" => Self::b(a, b), - _ => unreachable!(), - })) - } else { - unreachable!() - } -} else { - None -})); -impl Bsp { - #[inline] pub const fn n (a: A, b: B) -> Self { Self(North, a, b) } - #[inline] pub const fn s (a: A, b: B) -> Self { Self(South, a, b) } - #[inline] pub const fn e (a: A, b: B) -> Self { Self(East, a, b) } - #[inline] pub const fn w (a: A, b: B) -> Self { Self(West, a, b) } - #[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) } - #[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) } -} -pub trait BspAreas, B: Content> { - fn direction (&self) -> Direction; - fn contents (&self) -> (&A, &B); - fn areas (&self, outer: E::Area) -> [E::Area;3] { - let direction = self.direction(); - let [x, y, w, h] = outer.xywh(); - let (a, b) = self.contents(); - let [aw, ah] = a.layout(outer).wh(); - let [bw, bh] = b.layout(match direction { - Above | Below => outer, - South => [x, y + ah, w, h.minus(ah)].into(), - North => [x, y, w, h.minus(ah)].into(), - East => [x + aw, y, w.minus(aw), h].into(), - West => [x, y, w.minus(aw), h].into(), - }).wh(); - match direction { - Above | Below => { - let [x, y, w, h] = outer.center_xy([aw.max(bw), ah.max(bh)]); - let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah]; - let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - South => { - let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); - let a = [(x + w/2.into()).minus(aw/2.into()), y, aw, ah]; - let b = [(x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - North => { - let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); - let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah]; - let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - East => { - let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); - let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah]; - let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - West => { - let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); - let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah]; - let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - } - } -} -impl, B: Content> BspAreas for Bsp { - fn direction (&self) -> Direction { self.0 } - fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } -} -/// Renders multiple things on top of each other, -#[macro_export] macro_rules! lay { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} -} -/// Stack southward. -#[macro_export] macro_rules! col { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}; -} -/// Stack northward. -#[macro_export] macro_rules! col_up { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }} -} -/// Stack eastward. -#[macro_export] macro_rules! row { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; -} diff --git a/output/src/ops/either.rs b/output/src/ops/either.rs deleted file mode 100644 index 29ab0e8..0000000 --- a/output/src/ops/either.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::*; - -/// Show one item if a condition is true and another if the condition is false -pub struct Either(pub bool, pub A, pub B); -impl Either { - /// Create a ternary condition. - pub const fn new (c: bool, a: A, b: B) -> Self { - Self(c, a, b) - } -} -#[cfg(feature = "dsl")] take!(Either, A, B|state, words|Ok( -if let Some(Token { value: Value::Key("either"), .. }) = words.peek() { - let base = words.clone(); - let _ = words.next().unwrap(); - return Ok(Some(Self( - state.give_or_fail(words, ||"either: no condition")?, - state.give_or_fail(words, ||"either: no content 1")?, - state.give_or_fail(words, ||"either: no content 2")?, - ))) -} else { - None -})); -impl, B: Render> Content for Either { - fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, a, b) = self; - if *cond { a.layout(to) } else { b.layout(to) } - } - fn render (&self, to: &mut E) { - let Self(cond, a, b) = self; - if *cond { a.render(to) } else { b.render(to) } - } -} diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 593a518..e69de29 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -1,192 +0,0 @@ -//! [Content] items that modify the inherent -//! dimensions of their inner [Render]ables. -//! -//! Transform may also react to the [Area] taked. -//! ``` -//! use ::tengri::{output::*, tui::*}; -//! let area: [u16;4] = [10, 10, 20, 20]; -//! fn test (area: [u16;4], item: &impl Content, expected: [u16;4]) { -//! assert_eq!(Content::layout(item, area), expected); -//! assert_eq!(Render::layout(item, area), expected); -//! }; -//! test(area, &(), [20, 20, 0, 0]); -//! -//! test(area, &Fill::xy(()), area); -//! test(area, &Fill::x(()), [10, 20, 20, 0]); -//! test(area, &Fill::y(()), [20, 10, 0, 20]); -//! -//! //FIXME:test(area, &Fixed::x(4, ()), [18, 20, 4, 0]); -//! //FIXME:test(area, &Fixed::y(4, ()), [20, 18, 0, 4]); -//! //FIXME:test(area, &Fixed::xy(4, 4, unit), [18, 18, 4, 4]); -//! ``` - -use crate::*; - -/// Defines an enum that transforms its content -/// along either the X axis, the Y axis, or both. -macro_rules! transform_xy { - ($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$area:expr) => { - pub enum $Enum { X(A), Y(A), XY(A) } - impl $Enum { - #[inline] pub const fn x (item: A) -> Self { Self::X(item) } - #[inline] pub const fn y (item: A) -> Self { Self::Y(item) } - #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } - } - #[cfg(feature = "dsl")] take!($Enum, A|state, words|Ok( - if let Some(Token { value: Value::Key(k), .. }) = words.peek() { - let mut base = words.clone(); - let content = state.give_or_fail(words, ||format!("{k}: no content"))?; - return Ok(Some(match words.next() { - Some(Token{value: Value::Key($x),..}) => Self::x(content), - Some(Token{value: Value::Key($y),..}) => Self::y(content), - Some(Token{value: Value::Key($xy),..}) => Self::xy(content), - _ => unreachable!() - })) - } else { - None - })); - impl> Content for $Enum { - fn content (&self) -> impl Render + '_ { - match self { - Self::X(item) => item, - Self::Y(item) => item, - Self::XY(item) => item, - } - } - fn layout (&$self, $to: ::Area) -> ::Area { - use $Enum::*; - $area - } - } - } -} - -/// Defines an enum that parametrically transforms its content -/// along either the X axis, the Y axis, or both. -macro_rules! transform_xy_unit { - ($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$layout:expr) => { - pub enum $Enum { X(U, A), Y(U, A), XY(U, U, A), } - impl $Enum { - #[inline] pub const fn x (x: U, item: A) -> Self { Self::X(x, item) } - #[inline] pub const fn y (y: U, item: A) -> Self { Self::Y(y, item) } - #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } - } - #[cfg(feature = "dsl")] take!($Enum, U, A|state, words|Ok( - if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = words.peek() { - let mut base = words.clone(); - Some(match words.next() { - Some(Token { value: Value::Key($x), .. }) => Self::x( - state.give_or_fail(words, ||"x: no unit")?, - state.give_or_fail(words, ||"x: no content")?, - ), - Some(Token { value: Value::Key($y), .. }) => Self::y( - state.give_or_fail(words, ||"y: no unit")?, - state.give_or_fail(words, ||"y: no content")?, - ), - Some(Token { value: Value::Key($x), .. }) => Self::xy( - state.give_or_fail(words, ||"xy: no unit x")?, - state.give_or_fail(words, ||"xy: no unit y")?, - state.give_or_fail(words, ||"xy: no content")? - ), - _ => unreachable!(), - }) - } else { - None - })); - impl> Content for $Enum { - fn layout (&$self, $to: E::Area) -> E::Area { - $layout.into() - } - fn content (&self) -> impl Render + '_ { - use $Enum::*; - Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }) - } - } - impl $Enum { - #[inline] pub fn dx (&self) -> U { - use $Enum::*; - match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, } - } - #[inline] pub fn dy (&self) -> U { - use $Enum::*; - match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, } - } - } - } -} - -transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{ - let [x0, y0, wmax, hmax] = to.xywh(); - let [x, y, w, h] = self.content().layout(to).xywh(); - match self { - X(_) => [x0, y, wmax, h], - Y(_) => [x, y0, w, hmax], - XY(_) => [x0, y0, wmax, hmax], - }.into() -}); - -transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ - let [x, y, w, h] = area.xywh(); - let fixed_area = match self { - Self::X(fw, _) => [x, y, *fw, h], - Self::Y(fh, _) => [x, y, w, *fh], - Self::XY(fw, fh, _) => [x, y, *fw, *fh], - }; - let [x, y, w, h] = Render::layout(&self.content(), fixed_area.into()).xywh(); - let fixed_area = match self { - Self::X(fw, _) => [x, y, *fw, h], - Self::Y(fh, _) => [x, y, w, *fh], - Self::XY(fw, fh, _) => [x, y, *fw, *fh], - }; - fixed_area -}); - -transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{ - let area = Render::layout(&self.content(), area); - match self { - Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()], - Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)], - Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)], - } -}); - -transform_xy_unit!("max/x" "max/y" "max/xy"|self: Max, area|{ - let [x, y, w, h] = area.xywh(); - Render::layout(&self.content(), match self { - Self::X(fw, _) => [x, y, *fw, h], - Self::Y(fh, _) => [x, y, w, *fh], - Self::XY(fw, fh, _) => [x, y, *fw, *fh], - }.into()) -}); - -transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render::layout( - &self.content(), - [area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into())); - -transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout( - &self.content(), - [area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into())); - -transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{ - let area = Render::layout(&self.content(), area); - [area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()] -}); - -transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{ - let area = Render::layout(&self.content(), area); - [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] -}); - -transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{ - let area = Render::layout(&self.content(), area); - let dx = self.dx(); - let dy = self.dy(); - [area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))] -}); - -transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ - let area = Render::layout(&self.content(), area); - let dx = self.dx(); - let dy = self.dy(); - [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] -}); diff --git a/output/src/ops/when.rs b/output/src/ops/when.rs deleted file mode 100644 index 6324900..0000000 --- a/output/src/ops/when.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::*; -/// Show an item only when a condition is true. -pub struct When(pub bool, pub A); -impl When { - /// Create a binary condition. - pub const fn new (c: bool, a: A) -> Self { - Self(c, a) - } -} -#[cfg(feature = "dsl")]take!(When, A|state, words|Ok(Some(match words.peek() { - Some(Token { value: Value::Key("when"), .. }) => { - let _ = words.next(); - let base = words.clone(); - let cond = state.give_or_fail(words, ||"cond: no condition")?; - let cont = state.give_or_fail(words, ||"cond: no content")?; - Self(cond, cont) - }, - _ => return Ok(None) -}))); - -impl> Content for When { - fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, item) = self; - let mut area = E::Area::zero(); - if *cond { - let item_area = item.layout(to); - area[0] = item_area.x(); - area[1] = item_area.y(); - area[2] = item_area.w(); - area[3] = item_area.h(); - } - area.into() - } - fn render (&self, to: &mut E) { - let Self(cond, item) = self; - if *cond { item.render(to) } - } -} diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs new file mode 100644 index 0000000..d34cc34 --- /dev/null +++ b/output/src/ops_dsl.rs @@ -0,0 +1,185 @@ +use crate::*; + +impl Eval> for S where + S: Eval + Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(match source.peek() { + Some(Value::Key("when")) => Some(Self( + self.provide(source, ||"when: expected condition")?, + self.provide(source, ||"when: expected content")?, + )), + _ => None + }) + } +} + +impl Eval> for S where + S: Eval + Eval + Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(match source.peek() { + Some(Value::Key("either")) => Some(Self( + self.provide(source, ||"either: expected condition")?, + self.provide(source, ||"either: expected content 1")?, + self.provide(source, ||"either: expected content 2")? + )), + _ => None + }) + } +} + +impl Eval> for S where + S: Eval + Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(if let Some(Value::Key(key)) = source.peek() { + Some(match key { + "bsp/n" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/n: expected content 1")?; + let b: B = self.provide(source, ||"bsp/n: expected content 2")?; + Self::n(a, b) + }, + "bsp/s" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/s: expected content 1")?; + let b: B = self.provide(source, ||"bsp/s: expected content 2")?; + Self::s(a, b) + }, + "bsp/e" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/e: expected content 1")?; + let b: B = self.provide(source, ||"bsp/e: expected content 2")?; + Self::e(a, b) + }, + "bsp/w" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/w: expected content 1")?; + let b: B = self.provide(source, ||"bsp/w: expected content 2")?; + Self::w(a, b) + }, + "bsp/a" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/a: expected content 1")?; + let b: B = self.provide(source, ||"bsp/a: expected content 2")?; + Self::a(a, b) + }, + "bsp/b" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/b: expected content 1")?; + let b: B = self.provide(source, ||"bsp/b: expected content 2")?; + Self::b(a, b) + }, + _ => return Ok(None), + }) + } else { + None + }) + } +} + +impl Eval> for S where + S: Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(if let Some(Value::Key(key)) = source.peek() { + Some(match key { + "align/c" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/c: expected content")?; + Self::c(content) + }, + "align/x" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/x: expected content")?; + Self::x(content) + }, + "align/y" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/y: expected content")?; + Self::y(content) + }, + "align/n" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/n: expected content")?; + Self::n(content) + }, + "align/s" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/s: expected content")?; + Self::s(content) + }, + "align/e" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/e: expected content")?; + Self::e(content) + }, + "align/w" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/w: expected content")?; + Self::w(content) + }, + "align/nw" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/nw: expected content")?; + Self::nw(content) + }, + "align/ne" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/ne: expected content")?; + Self::ne(content) + }, + "align/sw" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/sw: expected content")?; + Self::sw(content) + }, + "align/se" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/se: expected content")?; + Self::se(content) + }, + _ => return Ok(None), + }) + } else { + None + }) + } +} + + //#[cfg(feature = "dsl")] take!($Enum, A|state, words|Ok( + //if let Some(Token { value: Value::Key(k), .. }) = words.peek() { + //let mut base = words.clone(); + //let content = state.give_or_fail(words, ||format!("{k}: no content"))?; + //return Ok(Some(match words.next() { + //Some(Token{value: Value::Key($x),..}) => Self::x(content), + //Some(Token{value: Value::Key($y),..}) => Self::y(content), + //Some(Token{value: Value::Key($xy),..}) => Self::xy(content), + //_ => unreachable!() + //})) + //} else { + //None + //})); + //#[cfg(feature = "dsl")] take!($Enum, U, A|state, words|Ok( + //if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = words.peek() { + //let mut base = words.clone(); + //Some(match words.next() { + //Some(Token { value: Value::Key($x), .. }) => Self::x( + //state.give_or_fail(words, ||"x: no unit")?, + //state.give_or_fail(words, ||"x: no content")?, + //), + //Some(Token { value: Value::Key($y), .. }) => Self::y( + //state.give_or_fail(words, ||"y: no unit")?, + //state.give_or_fail(words, ||"y: no content")?, + //), + //Some(Token { value: Value::Key($x), .. }) => Self::xy( + //state.give_or_fail(words, ||"xy: no unit x")?, + //state.give_or_fail(words, ||"xy: no unit y")?, + //state.give_or_fail(words, ||"xy: no content")? + //), + //_ => unreachable!(), + //}) + //} else { + //None + //})); diff --git a/output/src/space/direction.rs b/output/src/space/direction.rs index 90dc95e..ed9df46 100644 --- a/output/src/space/direction.rs +++ b/output/src/space/direction.rs @@ -1,4 +1,5 @@ use crate::*; +use crate::Direction::*; /// A cardinal direction. #[derive(Copy, Clone, PartialEq, Debug)] diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index ddbc205..b4ab9d7 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -149,10 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl<'state> ::tengri::dsl::Take<'state, #state> for #command_enum { - fn take <'source: 'state> ( - state: &#state, mut words: ::tengri::dsl::TokenIter<'source> - ) -> Perhaps { + impl ::tengri::dsl::Take<#state> for #command_enum { + fn take (state: &#state, mut words: ::tengri::dsl::Cst) -> Perhaps { let mut words = words.clone(); let token = words.next(); todo!()//Ok(match token { #(#matchers)* _ => None }) diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index f345e43..873b639 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,10 +85,8 @@ impl ToTokens for ExposeImpl { }); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl<'n> ::tengri::dsl::Take<'n, #state> for #t { - fn take <'source: 'n> ( - state: &#state, mut words: ::tengri::dsl::TokenIter<'source> - ) -> Perhaps { + impl ::tengri::dsl::Take<#state> for #t { + fn take (state: &#state, mut words: ::tengri::dsl::Cst) -> Perhaps { Ok(Some(match words.next().map(|x|x.value) { #predefined #(#values)* diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 578bbbf..9815c34 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -43,50 +43,38 @@ impl ToTokens for ViewDef { 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 }); + let builtin = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { + ::tengri::dsl::Value::Exp(_, expr) => return Ok(Some( + #builtin::take_or_fail(state, expr, ||"failed to load builtin")?.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::Value::Sym(#key) => Some(Box::new(Thunk::new( - move||#self_ty::#value(state))))})); + ::tengri::dsl::Value::Sym(#key) => return Ok(Some( + state.#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 /// which might correspond to a given [TokenStream], /// while taking [#self_ty]'s state into consideration. - impl<'state: 'static> Take<'state, #self_ty> for Box + 'state> { - fn take <'source: 'state> (state: &#self_ty, mut words: TokenIter<'source>) - -> Perhaps + 'state>> - { - //let state = self; + impl<'source, 'state: 'source> + Take<'state, 'source, #self_ty> + for Box + 'state> + { + fn take (state: &'state #self_ty, mut words: Cst<'source>) -> Perhaps { Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { - match value { - #(::tengri::dsl::Value::Exp(_, expr) => { - Give::<'state, #builtin>::give(state, expr)?.map(|value|value.boxed()) - },)* - #( - #exposed, - )* - _ => None - } + match value { #(#builtin)* #(#exposed)* _ => None } } else { None }) } } - /// Generated by [tengri_proc]. - /// - /// Delegates the rendering of [#self_ty] to the [#self_ty::view} method, - /// which you will need to implement, e.g. passing a [TokenIter] - /// containing a layout and keybindings config from user dirs. - impl ::tengri::output::Content<#output> for #self_ty { - fn content (&self) -> impl Render<#output> + '_ { - #self_ty::view(self) - } - } - // Original user-taked implementation: - #block }) } } @@ -96,11 +84,11 @@ fn builtins_with_holes () -> impl Iterator { } fn builtins_with_boxes () -> impl Iterator { - builtins_with(quote! { _ }, quote! { Box+'state> }) + builtins_with(quote! { _ }, quote! { Box> }) } fn builtins_with_boxes_output (o: TokenStream2) -> impl Iterator { - builtins_with(quote! { _ }, quote! { Box+'state> }) + builtins_with(quote! { _ }, quote! { Box> }) } fn builtins_with (n: TokenStream2, c: TokenStream2) -> impl Iterator {