From ab07fd2b4303e1c91325aa3f5bc0014ec5a82961 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 19:45:25 +0300 Subject: [PATCH 1/7] dsl: compact --- dsl/src/dsl.rs | 324 ++++++++++++++++++++++++++++++++++++ dsl/src/dsl_context.rs | 40 ----- dsl/src/dsl_error.rs | 15 -- dsl/src/dsl_iter.rs | 157 ----------------- dsl/src/dsl_macros.rs | 46 ----- dsl/src/dsl_token.rs | 120 ------------- dsl/src/lib.rs | 39 ++++- output/src/ops/transform.rs | 6 +- 8 files changed, 363 insertions(+), 384 deletions(-) create mode 100644 dsl/src/dsl.rs delete mode 100644 dsl/src/dsl_context.rs delete mode 100644 dsl/src/dsl_error.rs delete mode 100644 dsl/src/dsl_iter.rs delete mode 100644 dsl/src/dsl_macros.rs delete mode 100644 dsl/src/dsl_token.rs diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs new file mode 100644 index 0000000..b3d9448 --- /dev/null +++ b/dsl/src/dsl.rs @@ -0,0 +1,324 @@ +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: 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() + } +} + +/// 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 deleted file mode 100644 index 4acbf63..0000000 --- a/dsl/src/dsl_context.rs +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index 40b687d..0000000 --- a/dsl/src/dsl_error.rs +++ /dev/null @@ -1,15 +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), -} diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs deleted file mode 100644 index acdbe4b..0000000 --- a/dsl/src/dsl_iter.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! 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 deleted file mode 100644 index 93b2cea..0000000 --- a/dsl/src/dsl_macros.rs +++ /dev/null @@ -1,46 +0,0 @@ -/// 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 deleted file mode 100644 index e26ee93..0000000 --- a/dsl/src/dsl_token.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! [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 411974a..52f8281 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,3 +1,36 @@ +//! [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)] @@ -8,11 +41,7 @@ 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_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; +mod dsl; pub use self::dsl::*; #[cfg(test)] mod test_token_iter { use crate::*; diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 0dd2264..611d22b 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -43,7 +43,11 @@ 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 = get_content!(state => token); + let content = if let Some(content) = state.get_content(&token.value) { + content + } else { + panic!("no content corresponding to {:?}", &token.value); + }; return Some(match k { $x => Self::x(content), $y => Self::y(content), From 60c077102421ed8a7d6cb8674e4380ec6396ca84 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 20:02:24 +0300 Subject: [PATCH 2/7] proc, input, output: cleanup warnings --- input/src/input_dsl.rs | 3 +-- output/src/ops/cond.rs | 48 ++++++++++++++------------------------- proc/src/lib.rs | 14 ++++-------- proc/src/proc_command.rs | 49 ++++++++-------------------------------- proc/src/proc_expose.rs | 2 +- proc/src/proc_view.rs | 9 -------- 6 files changed, 32 insertions(+), 93 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index bb5346d..0095579 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -132,8 +132,7 @@ 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()); - Ok(()) + write!(f, "[InputMap: {} layer(s)]", self.layers.len()) } } diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index 521fdf8..5e71bef 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -22,18 +22,13 @@ 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 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)) + 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)) + )) } }); @@ -41,25 +36,16 @@ 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 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)) + 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)), + )) } }); diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 66dc0c7..f566fa0 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -2,23 +2,17 @@ #![feature(box_patterns)] extern crate proc_macro; -pub(crate) use std::collections::{BTreeMap, BTreeSet}; +pub(crate) use std::collections::BTreeMap; 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, TokenTree, - Ident, Span, Punct, Spacing::*, Group, Delimiter, Literal + TokenStream as TokenStream2, Ident, Span, Punct, Group, Delimiter, Spacing::* }; pub(crate) use syn::{ - 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_macro_input, 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}; diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 456d797..8d9d345 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -121,13 +121,12 @@ 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(); @@ -148,8 +147,8 @@ 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! { @@ -164,11 +163,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 @@ -189,8 +188,10 @@ impl CommandArm { fn to_implementation (&self) -> TokenStream2 { let ident = &self.0; let variant = self.to_enum_variant_unbind(); - let mut give_rest = write_quote(quote! { /*TODO*/ }); - let give_args = self.args().map(|(arg, ty)|write_quote(quote! { #arg, })).collect::>(); + let 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), }) } } @@ -216,35 +217,3 @@ 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 15e08e8..36fc418 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) } } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index b62d976..a1166ae 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -146,15 +146,6 @@ impl ToTokens for ViewArm { //} //} -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)) From 3bb38f2d27afc294349f72b1a2f7936bc9338d4f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 20:21:31 +0300 Subject: [PATCH 3/7] proc: view: list available on error --- proc/src/proc_view.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index a1166ae..25ec680 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -45,7 +45,16 @@ 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 exposed: Vec<_> = exposed.iter().map(|(k,v)|ViewArm(k.clone(), v.clone())).collect(); + 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() + ); for token in quote! { #block /// Generated by [tengri_proc]. @@ -59,10 +68,7 @@ impl ToTokens for ViewDef { fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) -> Option> { - match value { - #(#exposed)* - _ => panic!("expected Sym(content), got: {value:?}") - } + match value { #(#exposed)* _ => panic!(#error_msg) } } } } { From 20ccff13de1957c1268c6fec64048d8ae1767fd5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 21:13:46 +0300 Subject: [PATCH 4/7] proc: auto implement Context on command target Context and TryFromDsl overlap --- dsl/src/dsl.rs | 10 +++------- input/src/input_dsl.rs | 4 ++-- output/src/ops/transform.rs | 4 ++-- output/src/view.rs | 2 +- proc/src/proc_command.rs | 10 +++++++++- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index b3d9448..f4eedaf 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -18,24 +18,20 @@ pub type ParseResult = Result; pub trait TryFromDsl<'state, T>: Sized { fn try_from_expr <'source: 'state> ( - _state: &'state T, _iter: TokenIter<'source> + _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, iter) = value { - return Self::try_from_expr(state, iter.clone()) + if let Exp(0, mut iter) = value { + return Self::try_from_expr(state, &mut iter) } 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 { diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 0095579..cde5a15 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, exp_iter.clone()) { + if let Some(command) = C::try_from_expr(state, &mut exp_iter) { 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, exp_iter.clone()) { + if let Some(command) = C::try_from_expr(state, &mut exp_iter) { return Some(command) } } diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 611d22b..904eba4 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: TokenIter<'source>) + fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Option { let mut iter = iter.clone(); @@ -88,7 +88,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: TokenIter<'source>) -> Option { + fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut 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 4bf3d73..f2e9ef4 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: TokenIter<$lt_source> + $iter: &mut TokenIter<$lt_source> ) -> Option { let mut $iter = $iter.clone(); $body; diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 8d9d345..9504f91 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -70,7 +70,7 @@ impl ToTokens for CommandDef { /// Generated by [tengri_proc]. impl<'state> TryFromDsl<'state, #target> for #enumeration { fn try_from_expr <'source: 'state> ( - state: &'state #target, iter: TokenIter<'source> + state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> ) -> Option { let mut iter = iter.clone(); match iter.next() { @@ -80,6 +80,14 @@ 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 Command<#target> for #enumeration { fn execute (self, state: &mut #target) -> Perhaps { match self { From fe8ecf8a9822cafdd43e194fc25b681db09df62c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 22:43:12 +0300 Subject: [PATCH 5/7] input, proc: add full paths in macros --- input/src/input_handle.rs | 8 +++++--- proc/src/proc_command.rs | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/input/src/input_handle.rs b/input/src/input_handle.rs index 2a8fbcb..99bce29 100644 --- a/input/src/input_handle.rs +++ b/input/src/input_handle.rs @@ -18,15 +18,17 @@ pub trait Input: Send + Sync + Sized { /// Implement the [Handle] trait. #[macro_export] macro_rules! handle { (|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl Handle for $Struct { + impl ::tengri::input::Handle for $Struct { fn handle (&mut $self, $input: &E) -> Perhaps { $handler } } }; ($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl Handle<$E> for $Struct { - fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> { + impl ::tengri::input::Handle<$E> for $Struct { + fn handle (&mut $self, $input: &$E) -> + Perhaps<<$E as ::tengri::input::Input>::Handled> + { $handler } } diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 9504f91..cc5b134 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -68,7 +68,7 @@ impl ToTokens for CommandDef { } #block /// Generated by [tengri_proc]. - impl<'state> TryFromDsl<'state, #target> for #enumeration { + impl<'state> ::tengri::dsl::TryFromDsl<'state, #target> for #enumeration { fn try_from_expr <'source: 'state> ( state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> ) -> Option { @@ -88,7 +88,7 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc]. - impl Command<#target> for #enumeration { + impl ::tengri::input::Command<#target> for #enumeration { fn execute (self, state: &mut #target) -> Perhaps { match self { #(#implementations)* @@ -160,7 +160,7 @@ impl CommandArm { 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: Context::get(state, &mut iter).expect(#give_err), + #arg: ::tengri::dsl::Context::get(state, &mut iter).expect(#give_err), }); } out From 4a385b40ff778b4f23b4a989da94d45a88137ba0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 22:43:21 +0300 Subject: [PATCH 6/7] test subcommand handling --- Cargo.lock | 3 +++ tengri/Cargo.toml | 5 +++++ tengri/src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d9ccc29..0ba0ef4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -936,9 +936,12 @@ dependencies = [ name = "tengri" version = "0.13.0" dependencies = [ + "crossterm", + "tengri", "tengri_dsl", "tengri_input", "tengri_output", + "tengri_proc", "tengri_tui", ] diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml index 1c7d4eb..23e87f8 100644 --- a/tengri/Cargo.toml +++ b/tengri/Cargo.toml @@ -10,6 +10,11 @@ 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 c1c72a0..7703a14 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -2,3 +2,59 @@ #[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_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) + } + } + let mut test = Test { + keys: InputMap::new( + "(@a do-thing) (@b do-sub do-other-thing)".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!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('c'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + Ok(()) +} From cb8fd26922fd1cfad4ceadeb89e48544531a178e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 23:00:36 +0300 Subject: [PATCH 7/7] collect tests --- dsl/src/lib.rs | 16 ---- output/src/lib.rs | 1 - output/src/ops/bsp.rs | 6 +- proc/src/lib.rs | 190 ++++++++++++++++++++++++++++++++++++++-- proc/src/proc_expose.rs | 106 ---------------------- proc/src/proc_view.rs | 122 +------------------------- tengri/src/lib.rs | 47 +++++++++- tui/examples/tui.rs | 3 + 8 files changed, 234 insertions(+), 257 deletions(-) diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 52f8281..c685572 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -143,22 +143,6 @@ 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/output/src/lib.rs b/output/src/lib.rs index 064d8da..45bf90a 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -64,7 +64,6 @@ 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 0ab80c3..fe48efc 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -1,7 +1,11 @@ use crate::*; pub use Direction::*; /// A split or layer. -pub struct Bsp(Direction, X, Y); +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); diff --git a/proc/src/lib.rs b/proc/src/lib.rs index f566fa0..132d416 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -21,14 +21,7 @@ mod proc_view; mod proc_expose; mod proc_command; -#[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), - )) -} +#[cfg(test)] use syn::parse_quote as pq; #[proc_macro_attribute] pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { @@ -48,6 +41,15 @@ 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); @@ -67,3 +69,175 @@ 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_expose.rs b/proc/src/proc_expose.rs index 36fc418..4a11347 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -189,109 +189,3 @@ 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 25ec680..7fdd1fd 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 { - output: Ident, + pub(crate) output: Ident, } #[derive(Debug, Clone)] @@ -114,123 +114,3 @@ 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)); - //} -//} - -//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/src/lib.rs b/tengri/src/lib.rs index 7703a14..53850cd 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -24,6 +24,9 @@ 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 })) } @@ -32,11 +35,17 @@ 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-sub do-other-thing)".into() - ) + 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, @@ -50,11 +59,41 @@ modifiers: KeyModifiers::NONE, state: KeyEventState::NONE, })))?); - assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + 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 3869a8e..0d3ef75 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -43,6 +43,9 @@ 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] => {}