diff --git a/Cargo.lock b/Cargo.lock index 0ba0ef4..d9ccc29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -936,12 +936,9 @@ dependencies = [ name = "tengri" version = "0.13.0" dependencies = [ - "crossterm", - "tengri", "tengri_dsl", "tengri_input", "tengri_output", - "tengri_proc", "tengri_tui", ] diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs deleted file mode 100644 index f4eedaf..0000000 --- a/dsl/src/dsl.rs +++ /dev/null @@ -1,320 +0,0 @@ -use crate::*; -use thiserror::Error; - -pub type ParseResult = Result; - -#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum ParseError { - #[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), -} - -pub trait TryFromDsl<'state, T>: Sized { - fn try_from_expr <'source: 'state> ( - _state: &'state T, _iter: &mut TokenIter<'source> - ) -> Option { - None - } - fn try_from_atom <'source: 'state> ( - state: &'state T, value: Value<'source> - ) -> Option { - if let Exp(0, mut iter) = value { - return Self::try_from_expr(state, &mut iter) - } - None - } -} - -/// Map EDN tokens to parameters of a given type for a given context -pub trait Context<'state, U>: Sized { - fn get <'source> (&'state self, _iter: &mut TokenIter<'source>) -> Option { - None - } -} - -impl<'state, T: Context<'state, U>, U> Context<'state, U> for &T { - fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { - (*self).get(iter) - } -} - -impl<'state, T: Context<'state, U>, U> Context<'state, U> for Option { - fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { - self.as_ref().map(|s|s.get(iter)).flatten() - } -} - -/// 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) -> Result { - 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) -> Result { - 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)) - }) -} - -#[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(ParseError), - Num(usize), - Sym(&'source str), - Key(&'source str), - Str(&'source str), - Exp(usize, TokenIter<'source>), -} - -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) - //str_range(self.source, self.start, self.end()) - } - 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 value (&self) -> Value { - self.value - } - pub const fn error (self, error: ParseError) -> 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 mut token = self.grow(); - token.value = Key(token.slice_source(self.source)); - token - } - pub const fn grow_sym (self) -> Self { - let mut token = self.grow(); - token.value = Sym(token.slice_source(self.source)); - token - } - pub const fn grow_str (self) -> Self { - let mut token = self.grow(); - token.value = Str(token.slice_source(self.source)); - token - } - pub const fn grow_exp (self) -> Self { - let mut token = self.grow(); - if let Exp(depth, _) = token.value { - token.value = Exp(depth, TokenIter::new(token.slice_source_exp(self.source))); - } else { - unreachable!() - } - token - } - pub const fn grow_in (self) -> Self { - let mut token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - token.value = Value::Exp(depth.saturating_add(1), source) - } else { - unreachable!() - } - token - } - pub const fn grow_out (self) -> Self { - let mut token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - if depth > 0 { - token.value = Value::Exp(depth - 1, source) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - token - } -} diff --git a/dsl/src/dsl_context.rs b/dsl/src/dsl_context.rs new file mode 100644 index 0000000..4acbf63 --- /dev/null +++ b/dsl/src/dsl_context.rs @@ -0,0 +1,40 @@ +use crate::*; + +pub trait TryFromDsl<'state, T>: Sized { + fn try_from_expr <'source: 'state> ( + _state: &'state T, _iter: TokenIter<'source> + ) -> Option { + None + } + fn try_from_atom <'source: 'state> ( + state: &'state T, value: Value<'source> + ) -> Option { + if let Exp(0, iter) = value { + return Self::try_from_expr(state, iter.clone()) + } + None + } +} + +pub trait TryIntoDsl: Sized { + fn try_into_atom (&self) -> Option; +} + +/// Map EDN tokens to parameters of a given type for a given context +pub trait Context<'state, U>: Sized { + fn get <'source> (&'state self, _iter: &mut TokenIter<'source>) -> Option { + None + } +} + +impl<'state, T: Context<'state, U>, U> Context<'state, U> for &T { + fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { + (*self).get(iter) + } +} + +impl<'state, T: Context<'state, U>, U> Context<'state, U> for Option { + fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { + self.as_ref().map(|s|s.get(iter)).flatten() + } +} diff --git a/dsl/src/dsl_error.rs b/dsl/src/dsl_error.rs new file mode 100644 index 0000000..40b687d --- /dev/null +++ b/dsl/src/dsl_error.rs @@ -0,0 +1,15 @@ +use crate::*; +use thiserror::Error; +pub type ParseResult = Result; +#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum ParseError { + #[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), +} diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs new file mode 100644 index 0000000..acdbe4b --- /dev/null +++ b/dsl/src/dsl_iter.rs @@ -0,0 +1,157 @@ +//! The token iterator [TokenIter] allows you to get the +//! general-purpose syntactic [Token]s represented by the source text. +//! +//! Both iterators are `peek`able: +//! +//! ``` +//! let src = include_str!("../test.edn"); +//! let mut view = tengri_dsl::TokenIter::new(src); +//! assert_eq!(view.0.0, src); +//! assert_eq!(view.peek(), view.0.peek()) +//! ``` +use crate::*; + +/// 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 + } + } +} + +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) -> Result { + 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) -> Result { + 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_macros.rs b/dsl/src/dsl_macros.rs new file mode 100644 index 0000000..93b2cea --- /dev/null +++ b/dsl/src/dsl_macros.rs @@ -0,0 +1,46 @@ +/// 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; + } + } +} + +/// 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; + } + } +} + +#[macro_export] macro_rules! get_value { + ($state:expr => $token:expr) => { + if let Some(value) = $state.get(&$token.value) { + value + } else { + panic!("no value corresponding to {:?}", &$token.value); + } + } +} + +#[macro_export] macro_rules! get_content { + ($state:expr => $token:expr) => { + if let Some(content) = $state.get_content(&$token.value) { + content + } else { + panic!("no content corresponding to {:?}", &$token.value); + } + } +} + diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs new file mode 100644 index 0000000..e26ee93 --- /dev/null +++ b/dsl/src/dsl_token.rs @@ -0,0 +1,120 @@ +//! [Token]s are parsed substrings with an associated [Value]. +//! +//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] +//! * [Value::Exp] wraps an expression depth and a [SourceIter] +//! 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 +//! * [Value::Sym] and [Value::Key] are stringish literals +//! with slightly different parsing rules. +//! * [Value::Num] is an unsigned integer literal. +//!``` +//! use tengri_dsl::{*, Value::*}; +//! let source = include_str!("../test.edn"); +//! let mut view = TokenIter::new(source); +//! assert_eq!(view.peek(), Some(Token { +//! source, +//! start: 0, +//! length: source.len(), +//! value: Exp(0, TokenIter::new(&source[1..])) +//! })); +//!``` +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(ParseError), + Num(usize), + Sym(&'source str), + Key(&'source str), + Str(&'source str), + Exp(usize, TokenIter<'source>), +} + +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) + //str_range(self.source, self.start, self.end()) + } + 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 value (&self) -> Value { + self.value + } + pub const fn error (self, error: ParseError) -> 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 mut token = self.grow(); + token.value = Key(token.slice_source(self.source)); + token + } + pub const fn grow_sym (self) -> Self { + let mut token = self.grow(); + token.value = Sym(token.slice_source(self.source)); + token + } + pub const fn grow_str (self) -> Self { + let mut token = self.grow(); + token.value = Str(token.slice_source(self.source)); + token + } + pub const fn grow_exp (self) -> Self { + let mut token = self.grow(); + if let Exp(depth, _) = token.value { + token.value = Exp(depth, TokenIter::new(token.slice_source_exp(self.source))); + } else { + unreachable!() + } + token + } + pub const fn grow_in (self) -> Self { + let mut token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + token.value = Value::Exp(depth.saturating_add(1), source) + } else { + unreachable!() + } + token + } + pub const fn grow_out (self) -> Self { + let mut token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + if depth > 0 { + token.value = Value::Exp(depth - 1, source) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + token + } +} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index c685572..411974a 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,36 +1,3 @@ -//! [Token]s are parsed substrings with an associated [Value]. -//! -//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] -//! * [Value::Exp] wraps an expression depth and a [SourceIter] -//! 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 -//! * [Value::Sym] and [Value::Key] are stringish literals -//! with slightly different parsing rules. -//! * [Value::Num] is an unsigned integer literal. -//!``` -//! use tengri_dsl::{*, Value::*}; -//! let source = include_str!("../test.edn"); -//! let mut view = TokenIter::new(source); -//! assert_eq!(view.peek(), Some(Token { -//! source, -//! start: 0, -//! length: source.len(), -//! value: Exp(0, TokenIter::new(&source[1..])) -//! })); -//!``` -//! The token iterator [TokenIter] allows you to get the -//! general-purpose syntactic [Token]s represented by the source text. -//! -//! Both iterators are `peek`able: -//! -//! ``` -//! let src = include_str!("../test.edn"); -//! let mut view = tengri_dsl::TokenIter::new(src); -//! assert_eq!(view.0.0, src); -//! assert_eq!(view.peek(), view.0.peek()) -//! ``` #![feature(adt_const_params)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] @@ -41,7 +8,11 @@ pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use std::fmt::Debug; -mod dsl; pub use self::dsl::*; +mod dsl_error; pub use self::dsl_error::*; +mod dsl_token; pub use self::dsl_token::*; +mod dsl_iter; pub use self::dsl_iter::*; +mod dsl_context; pub use self::dsl_context::*; +mod dsl_macros; #[cfg(test)] mod test_token_iter { use crate::*; @@ -143,6 +114,22 @@ mod dsl; pub use self::dsl::*; Ok(()) } +#[cfg(test)] #[test] fn test_dsl_context () { + struct Test; + #[tengri_proc::expose] + impl Test { + fn some_bool (&self) -> bool { + true + } + } + assert_eq!(Test.get(&Value::Sym(":false")), Some(false)); + assert_eq!(Test.get(&Value::Sym(":true")), Some(true)); + assert_eq!(Test.get(&Value::Sym(":some-bool")), Some(true)); + assert_eq!(Test.get(&Value::Sym(":missing-bool")), None); + assert_eq!(Test.get(&Value::Num(0)), Some(false)); + assert_eq!(Test.get(&Value::Num(1)), Some(true)); +} + //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { //// Let's pretend to render some view. //let source = include_str!("../../tek/src/view_arranger.edn"); diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index cde5a15..bb5346d 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -29,7 +29,7 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::try_from_expr(state, &mut exp_iter) { + if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { return Some(command) } } @@ -55,7 +55,7 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::try_from_expr(state, &mut exp_iter) { + if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { return Some(command) } } @@ -132,7 +132,8 @@ where M: KeyMap<'state, S, C, I> + Send + Sync { fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(f, "[InputMap: {} layer(s)]", self.layers.len()) + write!(f, "[InputMap: {} layer(s)]", self.layers.len()); + Ok(()) } } diff --git a/input/src/input_handle.rs b/input/src/input_handle.rs index 99bce29..2a8fbcb 100644 --- a/input/src/input_handle.rs +++ b/input/src/input_handle.rs @@ -18,17 +18,15 @@ pub trait Input: Send + Sync + Sized { /// Implement the [Handle] trait. #[macro_export] macro_rules! handle { (|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl ::tengri::input::Handle for $Struct { + impl Handle for $Struct { fn handle (&mut $self, $input: &E) -> Perhaps { $handler } } }; ($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl ::tengri::input::Handle<$E> for $Struct { - fn handle (&mut $self, $input: &$E) -> - Perhaps<<$E as ::tengri::input::Input>::Handled> - { + impl Handle<$E> for $Struct { + fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> { $handler } } diff --git a/output/src/lib.rs b/output/src/lib.rs index 45bf90a..064d8da 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -64,6 +64,7 @@ pub type Perhaps = Result, Box>; #[cfg(test)] mod test { use crate::*; use proptest::{prelude::*, option::of}; + use proptest::option::of; proptest! { #[test] fn proptest_direction ( diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index fe48efc..0ab80c3 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -1,11 +1,7 @@ use crate::*; pub use Direction::*; /// A split or layer. -pub struct Bsp( - pub(crate) Direction, - pub(crate) A, - pub(crate) B, -); +pub struct Bsp(Direction, X, Y); impl, B: Content> Content for Bsp { fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index 5e71bef..521fdf8 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -22,13 +22,18 @@ impl Either { try_from_expr!(<'source, 'state, E>: When>: |state, iter| { if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() { let _ = iter.next().unwrap(); - let content = iter.next().expect("no content specified").value; - return Some(Self( - state.get(&mut iter) - .expect("no condition provided"), - state.get_content(&content) - .unwrap_or_else(||panic!("no content corresponding to for {:?}", &content)) - )) + + let condition = iter.next().expect("no condition specified"); + let condition = state.get(&mut iter).expect("no condition provided"); + + let content = iter.next().expect("no content specified"); + let content = if let Some(content) = state.get_content(&content.value) { + content + } else { + panic!("no content corresponding to for {:?}", &content); + }; + + return Some(Self(condition, content)) } }); @@ -36,16 +41,25 @@ try_from_expr!(<'source, 'state, E>: When>: |state, iter| { try_from_expr!(<'source, 'state, E>: Either, RenderBox<'state, E>>: |state, iter| { if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() { let _ = iter.next().unwrap(); - let content = iter.next().expect("no content specified").value; - let alternate = iter.next().expect("no alternate specified").value; - return Some(Self( - state.get(&mut iter) - .expect("no condition provided"), - state.get_content(&content) - .unwrap_or_else(||panic!("no content 1 corresponding to {:?}", &content)), - state.get_content(&alternate) - .unwrap_or_else(||panic!("no content 2 corresponding to {:?}", &alternate)), - )) + + let condition = iter.next().expect("no condition specified"); + let condition = state.get(&mut iter).expect("no condition provided"); + + let content = iter.next().expect("no content specified"); + let content = if let Some(content) = state.get_content(&content.value) { + content + } else { + panic!("no content 1 corresponding to {:?}", &content); + }; + + let alternate = iter.next().expect("no alternate specified"); + let alternate = if let Some(alternate) = state.get_content(&alternate.value) { + alternate + } else { + panic!("no content 2 corresponding to {:?}", &alternate); + }; + + return Some(Self(condition, content, alternate)) } }); diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 904eba4..0dd2264 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -35,7 +35,7 @@ macro_rules! transform_xy { #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> for $Enum> { - fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) + fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>) -> Option { let mut iter = iter.clone(); @@ -43,11 +43,7 @@ macro_rules! transform_xy { if k == $x || k == $y || k == $xy { let _ = iter.next().unwrap(); let token = iter.next().expect("no content specified"); - let content = if let Some(content) = state.get_content(&token.value) { - content - } else { - panic!("no content corresponding to {:?}", &token.value); - }; + let content = get_content!(state => token); return Some(match k { $x => Self::x(content), $y => Self::y(content), @@ -88,7 +84,7 @@ macro_rules! transform_xy_unit { #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> for $Enum> { - fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Option { + fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>) -> Option { let mut iter = iter.clone(); if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { if k == $x || k == $y { diff --git a/output/src/view.rs b/output/src/view.rs index f2e9ef4..4bf3d73 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -103,7 +103,7 @@ pub trait ViewContext<'state, E: Output + 'state>: Send + Sync > TryFromDsl<$lt_state, T> for $Struct { fn try_from_expr <$lt_source: $lt_state> ( $state: &$lt_state T, - $iter: &mut TokenIter<$lt_source> + $iter: TokenIter<$lt_source> ) -> Option { let mut $iter = $iter.clone(); $body; diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 132d416..66dc0c7 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -2,17 +2,23 @@ #![feature(box_patterns)] extern crate proc_macro; -pub(crate) use std::collections::BTreeMap; +pub(crate) use std::collections::{BTreeMap, BTreeSet}; pub(crate) use std::cmp::Ordering; pub(crate) use std::sync::Arc; pub(crate) use proc_macro::TokenStream; pub(crate) use proc_macro2::{ - TokenStream as TokenStream2, Ident, Span, Punct, Group, Delimiter, Spacing::* + TokenStream as TokenStream2, TokenTree, + Ident, Span, Punct, Spacing::*, Group, Delimiter, Literal }; pub(crate) use syn::{ - parse_macro_input, ImplItem, ImplItemFn, LitStr, Type, - ItemImpl, ReturnType, Signature, FnArg, Pat, PatType, PatIdent, + parse, parse_macro_input, parse_quote as pq, + braced, bracketed, parenthesized, Token, + Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, + ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, FnArg, + Pat, PatType, PatIdent, parse::{Parse, ParseStream, Result}, + token::{PathSep, Brace}, + punctuated::Punctuated, }; pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; pub(crate) use heck::{AsKebabCase, AsUpperCamelCase}; @@ -21,7 +27,14 @@ mod proc_view; mod proc_expose; mod proc_command; -#[cfg(test)] use syn::parse_quote as pq; +#[proc_macro_attribute] +pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { + use self::proc_view::{ViewDef, ViewMeta, ViewImpl}; + write(ViewDef( + parse_macro_input!(meta as ViewMeta), + parse_macro_input!(item as ViewImpl), + )) +} #[proc_macro_attribute] pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { @@ -41,15 +54,6 @@ pub fn command (meta: TokenStream, item: TokenStream) -> TokenStream { )) } -#[proc_macro_attribute] -pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { - use self::proc_view::{ViewDef, ViewMeta, ViewImpl}; - write(ViewDef( - parse_macro_input!(meta as ViewMeta), - parse_macro_input!(item as ViewImpl), - )) -} - pub(crate) fn write (t: T) -> TokenStream { let mut out = TokenStream2::new(); t.to_tokens(&mut out); @@ -69,175 +73,3 @@ pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { out.append(token); } } - -#[cfg(test)] #[test] fn test_proc_view () { - let x: crate::proc_view::ViewMeta = pq! { SomeOutput }; - let output: Ident = pq! { SomeOutput }; - assert_eq!(x.output, output); - - // TODO - let x: crate::proc_view::ViewImpl = pq! { - impl Foo { - /// docstring1 - #[tengri::view(":view1")] #[bar] fn a_view () {} - - #[baz] - /// docstring2 - #[baz] fn is_not_view () {} - } - }; - let expected_target: Ident = pq! { Foo }; - //assert_eq!(x.target, expected_target); - //assert_eq!(x.items.len(), 2); - //assert_eq!(x.items[0].item, pq! { - ///// docstring1 - //#[bar] fn a_view () {} - //}); - //assert_eq!(x.items[1].item, pq! { - //#[baz] - ///// docstring2 - //#[baz] fn is_not_view () {} - //}); - //assert_eq!(x.syms, vec![ - //ViewArm( { symbol: pq! { ":view1" }, name: pq! { a_view }, }, - //]); - // FIXME - //let parsed: ViewDefinition = pq! { - //#[tengri_proc::view(SomeOutput)] - //impl SomeView { - //#[tengri::view(":view-1")] - //fn view_1 (&self) -> impl Content + use<'_> { - //"view-1" - //} - //} - //}; - //let written = quote! { #parsed }; - //assert_eq!(format!("{written}"), format!("{}", quote! { - //impl SomeView { - //fn view_1 (&self) -> impl Content + use<'_> { - //"view-1" - //} - //} - ///// Generated by [tengri_proc]. - //impl ::tengri::output::Content for SomeView { - //fn content (&self) -> impl Render { - //self.size.of(::tengri::output::View(self, self.config.view)) - //} - //} - ///// Generated by [tengri_proc]. - //impl<'a> ::tengri::dsl::ViewContext<'a, SomeOutput> for SomeView { - //fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { - //match value { - //::tengri::dsl::Value::Sym(":view-1") => self.view_1().boxed(), - //_ => panic!("expected Sym(content), got: {value:?}") - //} - //} - //} - //})); -} - -//#[cfg(test)] #[test] fn test_expose_definition () { - // TODO - //let parsed: ExposeImpl = pq! { - ////#[tengri_proc::expose] - //impl Something { - //fn something () -> bool {} - //} - //}; - //// FIXME: - ////assert_eq!( - ////format!("{}", quote! { #parsed }), - ////format!("{}", quote! { - ////impl Something { - ////fn something () {} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Sym(":true") => true, - ////::tengri::Value::Sym(":false") => false, - ////::tengri::Value::Sym(":bool1") => true || false, - ////_ => return None - ////}) - ////} - ////} - ////}) - ////); - - //let parsed: ExposeImpl = pq! { - ////#[tengri_proc::expose] - //impl Something { - //#[tengri::expose(bool)] { - //":bool1" => true || false, - //} - //#[tengri::expose(u16)] { - //":u161" => 0 + 1, - //} - //#[tengri::expose(usize)] { - //":usize1" => 1 + 2, - //} - //#[tengri::expose(Arc)] { - //":arcstr1" => "foo".into(), - //} - //#[tengri::expose(Option>)] { - //":optarcstr1" => Some("bar".into()), - //":optarcstr2" => Some("baz".into()), - //} - //fn something () {} - //} - //}; - //// FIXME: - ////assert_eq!( - ////format!("{}", quote! { #parsed }), - ////format!("{}", quote! { - ////impl Something { - ////fn something () {} - ////} - ////impl ::tengri::Context> for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option> { - ////Some(match dsl { - ////::tengri::Value::Sym(":arcstr1") => "foo".into(), - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context>> for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option>> { - ////Some(match dsl { - ////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), - ////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Sym(":true") => true, - ////::tengri::Value::Sym(":false") => false, - ////::tengri::Value::Sym(":bool1") => true || false, - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Num(n) => *n as u16, - ////::tengri::Value::Sym(":u161") => 0 + 1, - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Num(n) => *n as usize, - ////::tengri::Value::Sym(":usize1") => 1 + 2, - ////_ => return None - ////}) - ////} - ////} - ////}) - ////) -//} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index cc5b134..456d797 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -68,9 +68,9 @@ impl ToTokens for CommandDef { } #block /// Generated by [tengri_proc]. - impl<'state> ::tengri::dsl::TryFromDsl<'state, #target> for #enumeration { + impl<'state> TryFromDsl<'state, #target> for #enumeration { fn try_from_expr <'source: 'state> ( - state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> + state: &'state #target, iter: TokenIter<'source> ) -> Option { let mut iter = iter.clone(); match iter.next() { @@ -80,15 +80,7 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc]. - impl<'state> ::tengri::dsl::Context<'state, #enumeration> for #target { - fn get <'source> (&self, iter: &mut ::tengri::dsl::TokenIter<'source>) - -> Option<#enumeration> - { - #enumeration::try_from_expr(self, iter) - } - } - /// Generated by [tengri_proc]. - impl ::tengri::input::Command<#target> for #enumeration { + impl Command<#target> for #enumeration { fn execute (self, state: &mut #target) -> Perhaps { match self { #(#implementations)* @@ -129,12 +121,13 @@ impl CommandArm { Some((arg, ty)) } else { unreachable!("only typed args should be present at this position"); + None }) } fn to_enum_variant_def (&self) -> TokenStream2 { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); - //let ident = &self.0; + let ident = &self.0; if self.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); @@ -155,12 +148,12 @@ impl CommandArm { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for (arg, ty) in self.args() { - //let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", - //quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); + let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); let give_err = LitStr::new(&format!("{}: missing value for \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { - #arg: ::tengri::dsl::Context::get(state, &mut iter).expect(#give_err), + #arg: Context::get(state, &mut iter).expect(#give_err), }); } out @@ -171,11 +164,11 @@ impl CommandArm { fn to_enum_variant_unbind (&self) -> TokenStream2 { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); - //let ident = &self.0; + let ident = &self.0; if self.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); - for (arg, _ty) in self.args() { + for (arg, ty) in self.args() { write_quote_to(&mut out, quote! { #arg , }); } out @@ -196,10 +189,8 @@ impl CommandArm { fn to_implementation (&self) -> TokenStream2 { let ident = &self.0; let variant = self.to_enum_variant_unbind(); - let give_rest = write_quote(quote! { /*TODO*/ }); - let give_args = self.args() - .map(|(arg, _ty)|write_quote(quote! { #arg, })) - .collect::>(); + let mut give_rest = write_quote(quote! { /*TODO*/ }); + let give_args = self.args().map(|(arg, ty)|write_quote(quote! { #arg, })).collect::>(); write_quote(quote! { Self::#variant => Self::#ident(state, #(#give_args)* #give_rest), }) } } @@ -225,3 +216,35 @@ impl ToTokens for CommandVariant { out.append(Punct::new(',', Alone)); } } + +impl ToTokens for CommandArm { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(ident, args, returnType) = self; + for ident in ["tengri", "dsl", "Value", "Sym"].iter() { + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new(ident, Span::call_site())); + } + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + out.append(self.to_enum_variant_ident()); + out + })); + out.append(Punct::new('=', Joint)); + out.append(Punct::new('>', Alone)); + out.append(Ident::new("Self", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(ident.clone()); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + for arg in args.iter() { + // TODO + out.append(LitStr::new(&self.to_key(), Span::call_site()).token()); + out.append(Punct::new(',', Alone)); + } + out + })); + out.append(Punct::new(',', Alone)); + } +} diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 4a11347..15e08e8 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -19,7 +19,7 @@ struct ExposeSym(LitStr); struct ExposeType(Box); impl Parse for ExposeMeta { - fn parse (_input: ParseStream) -> Result { + fn parse (input: ParseStream) -> Result { Ok(Self) } } @@ -189,3 +189,109 @@ impl ToTokens for ExposeType { self.0.to_tokens(out) } } + +#[cfg(test)] #[test] fn test_expose_definition () { + // TODO + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //fn something () -> bool {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////); + + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //#[tengri::expose(bool)] { + //":bool1" => true || false, + //} + //#[tengri::expose(u16)] { + //":u161" => 0 + 1, + //} + //#[tengri::expose(usize)] { + //":usize1" => 1 + 2, + //} + //#[tengri::expose(Arc)] { + //":arcstr1" => "foo".into(), + //} + //#[tengri::expose(Option>)] { + //":optarcstr1" => Some("bar".into()), + //":optarcstr2" => Some("baz".into()), + //} + //fn something () {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option> { + ////Some(match dsl { + ////::tengri::Value::Sym(":arcstr1") => "foo".into(), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context>> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option>> { + ////Some(match dsl { + ////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), + ////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as u16, + ////::tengri::Value::Sym(":u161") => 0 + 1, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as usize, + ////::tengri::Value::Sym(":usize1") => 1 + 2, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////) +} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 7fdd1fd..b62d976 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -5,7 +5,7 @@ pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl); #[derive(Debug, Clone)] pub(crate) struct ViewMeta { - pub(crate) output: Ident, + output: Ident, } #[derive(Debug, Clone)] @@ -45,16 +45,7 @@ impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let ident = &block.self_ty; - let mut available = vec![]; - let exposed: Vec<_> = exposed.iter().map(|(k,v)|{ - available.push(k.clone()); - ViewArm(k.clone(), v.clone()) - }).collect(); - let available: String = available.join(", "); - let error_msg = LitStr::new( - &format!("expected Sym(content), got: {{value:?}}, available: {available}"), - Span::call_site() - ); + let exposed: Vec<_> = exposed.iter().map(|(k,v)|ViewArm(k.clone(), v.clone())).collect(); for token in quote! { #block /// Generated by [tengri_proc]. @@ -68,7 +59,10 @@ impl ToTokens for ViewDef { fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) -> Option> { - match value { #(#exposed)* _ => panic!(#error_msg) } + match value { + #(#exposed)* + _ => panic!("expected Sym(content), got: {value:?}") + } } } } { @@ -114,3 +108,132 @@ impl ToTokens for ViewArm { out.append(Punct::new(',', Alone)); } } + +//impl ToTokens for ViewSym { + //fn to_tokens (&self, out: &mut TokenStream2) { + //out.append(Punct::new(':', Joint)); + //out.append(Punct::new(':', Alone)); + //out.append(Ident::new("tengri", Span::call_site())); + //out.append(Punct::new(':', Joint)); + //out.append(Punct::new(':', Alone)); + //out.append(Ident::new("dsl", Span::call_site())); + //out.append(Punct::new(':', Joint)); + //out.append(Punct::new(':', Alone)); + //out.append(Ident::new("Value", Span::call_site())); + //out.append(Punct::new(':', Joint)); + //out.append(Punct::new(':', Alone)); + //out.append(Ident::new("Sym", Span::call_site())); + //out.append(Group::new(Delimiter::Parenthesis, { + //let mut out = TokenStream2::new(); + //out.append(self.symbol.clone()); + //out + //})); + //out.append(Punct::new('=', Joint)); + //out.append(Punct::new('>', Alone)); + //out.append(Ident::new("Some", Span::call_site())); + //out.append(Group::new(Delimiter::Parenthesis, { + //let mut out = TokenStream2::new(); + //out.append(Ident::new("self", Span::call_site())); + //out.append(Punct::new('.', Alone)); + //out.append(self.name.clone()); + //out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); + //out.append(Punct::new('.', Alone)); + //out.append(Ident::new("boxed", Span::call_site())); + //out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); + //out + //})); + //out.append(Punct::new(',', Alone)); + //} +//} + +fn nth_segment_is (segments: &Punctuated, n: usize, x: &str) -> bool { + if let Some(PathSegment { arguments: PathArguments::None, ident, .. }) = segments.get(n) { + if format!("{ident}") == x { + return true + } + } + return false +} + +//impl std::cmp::PartialEq for ViewItem { + //fn eq (&self, other: &Self) -> bool { + //self.item == other.item && (format!("{:?}", self.expose) == format!("{:?}", other.expose)) + //} +//} + +//impl std::cmp::PartialEq for ViewSym { + //fn eq (&self, other: &Self) -> bool { + //self.name == other.name && (format!("{}", self.symbol) == format!("{}", other.symbol)) + //} +//} + +#[cfg(test)] #[test] fn test_view_meta () { + let x: ViewMeta = pq! { SomeOutput }; + let output: Ident = pq! { SomeOutput }; + assert_eq!(x.output, output); +} + +#[cfg(test)] #[test] fn test_view_impl () { + // TODO + let x: ViewImpl = pq! { + impl Foo { + /// docstring1 + #[tengri::view(":view1")] #[bar] fn a_view () {} + + #[baz] + /// docstring2 + #[baz] fn is_not_view () {} + } + }; + let expected_target: Ident = pq! { Foo }; + //assert_eq!(x.target, expected_target); + //assert_eq!(x.items.len(), 2); + //assert_eq!(x.items[0].item, pq! { + ///// docstring1 + //#[bar] fn a_view () {} + //}); + //assert_eq!(x.items[1].item, pq! { + //#[baz] + ///// docstring2 + //#[baz] fn is_not_view () {} + //}); + //assert_eq!(x.syms, vec![ + //ViewArm( { symbol: pq! { ":view1" }, name: pq! { a_view }, }, + //]); +} + +#[cfg(test)] #[test] fn test_view_definition () { + // FIXME + //let parsed: ViewDefinition = pq! { + //#[tengri_proc::view(SomeOutput)] + //impl SomeView { + //#[tengri::view(":view-1")] + //fn view_1 (&self) -> impl Content + use<'_> { + //"view-1" + //} + //} + //}; + //let written = quote! { #parsed }; + //assert_eq!(format!("{written}"), format!("{}", quote! { + //impl SomeView { + //fn view_1 (&self) -> impl Content + use<'_> { + //"view-1" + //} + //} + ///// Generated by [tengri_proc]. + //impl ::tengri::output::Content for SomeView { + //fn content (&self) -> impl Render { + //self.size.of(::tengri::output::View(self, self.config.view)) + //} + //} + ///// Generated by [tengri_proc]. + //impl<'a> ::tengri::dsl::ViewContext<'a, SomeOutput> for SomeView { + //fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + //match value { + //::tengri::dsl::Value::Sym(":view-1") => self.view_1().boxed(), + //_ => panic!("expected Sym(content), got: {value:?}") + //} + //} + //} + //})); +} diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml index 23e87f8..1c7d4eb 100644 --- a/tengri/Cargo.toml +++ b/tengri/Cargo.toml @@ -10,11 +10,6 @@ tengri_input = { optional = true, path = "../input" } tengri_output = { optional = true, path = "../output" } tengri_tui = { optional = true, path = "../tui" } -[dev-dependencies] -tengri_proc = { path = "../proc" } -tengri = { path = ".", features = [ "dsl" ] } -crossterm = "0.28.1" - [features] default = [ "input", "output", "tui" ] input = [ "tengri_input" ] diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index 53850cd..c1c72a0 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -2,98 +2,3 @@ #[cfg(feature="input")] pub use ::tengri_input as input; #[cfg(feature="dsl")] pub use ::tengri_dsl as dsl; #[cfg(feature="tui")] pub use ::tengri_tui as tui; - -#[cfg(test)] extern crate tengri_proc; -#[cfg(test)] #[test] fn test_subcommand () -> crate::output::Usually<()> { - use crate::output::Perhaps; - use crate::input::{Command, InputMap, KeyMap, Handle, handle}; - use crate::dsl::{TryFromDsl, TokenIter}; - use crate::tui::TuiIn; - use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; - //use crate::input::*; - //use crate::dsl::*; - struct Test { - keys: InputMap<'static, Test, TestCommand, TuiIn, TokenIter<'static>> - } - handle!(TuiIn: |self: Test, input|if let Some(command) = self.keys.command(self, input) { - Ok(Some(true)) - } else { - Ok(None) - }); - #[tengri_proc::command(Test)] impl TestCommand { - fn do_thing (state: &mut Test) -> Perhaps { - Ok(None) - } - fn do_thing_arg (state: &mut Test, arg: usize) -> Perhaps { - Ok(None) - } - fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { - Ok(command.execute(state)?.map(|command|Self::DoSub { command })) - } - } - #[tengri_proc::command(Test)] impl TestSubcommand { - fn do_other_thing (state: &mut Test) -> Perhaps { - Ok(None) - } - fn do_other_thing_arg (state: &mut Test, arg: usize) -> Perhaps { - Ok(None) - } - } - let mut test = Test { - keys: InputMap::new(" - (@a do-thing) - (@b do-thing-arg 0) - (@c do-sub do-other-thing) - (@d do-sub do-other-thing-arg 0) - ".into()) - }; - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('a'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('b'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('c'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('d'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('z'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - Ok(()) -} - -#[cfg(test)] #[test] fn test_dsl_context () { - use crate::dsl::Value; - - struct Test; - #[tengri_proc::expose] - impl Test { - fn some_bool (&self) -> bool { - true - } - } - assert_eq!(Test.get(&Value::Sym(":false")), Some(false)); - assert_eq!(Test.get(&Value::Sym(":true")), Some(true)); - assert_eq!(Test.get(&Value::Sym(":some-bool")), Some(true)); - assert_eq!(Test.get(&Value::Sym(":missing-bool")), None); - assert_eq!(Test.get(&Value::Num(0)), Some(false)); - assert_eq!(Test.get(&Value::Num(1)), Some(true)); -} diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index 0d3ef75..3869a8e 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -43,9 +43,6 @@ handle!(TuiIn: |self: Example, input|{ #[tengri_proc::expose] impl Example { - fn _todo_u16_stub (&self) -> u16 { todo!() } - fn _todo_bool_stub (&self) -> bool { todo!() } - fn _todo_usize_stub (&self) -> usize { todo!() } //[bool] => {} //[u16] => {} //[usize] => {}