Compare commits

..

7 commits

Author SHA1 Message Date
cb8fd26922 collect tests
Some checks are pending
/ build (push) Waiting to run
2025-05-09 23:00:36 +03:00
4a385b40ff test subcommand handling 2025-05-09 22:43:21 +03:00
fe8ecf8a98 input, proc: add full paths in macros 2025-05-09 22:43:12 +03:00
20ccff13de proc: auto implement Context on command target
Context and TryFromDsl overlap
2025-05-09 21:13:52 +03:00
3bb38f2d27 proc: view: list available on error 2025-05-09 20:21:31 +03:00
60c0771024 proc, input, output: cleanup warnings 2025-05-09 20:02:24 +03:00
ab07fd2b43 dsl: compact 2025-05-09 19:45:25 +03:00
22 changed files with 718 additions and 747 deletions

3
Cargo.lock generated
View file

@ -936,9 +936,12 @@ dependencies = [
name = "tengri"
version = "0.13.0"
dependencies = [
"crossterm",
"tengri",
"tengri_dsl",
"tengri_input",
"tengri_output",
"tengri_proc",
"tengri_tui",
]

320
dsl/src/dsl.rs Normal file
View file

@ -0,0 +1,320 @@
use crate::*;
use thiserror::Error;
pub type ParseResult<T> = Result<T, ParseError>;
#[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<Self> {
None
}
fn try_from_atom <'source: 'state> (
state: &'state T, value: Value<'source>
) -> Option<Self> {
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<U> {
None
}
}
impl<'state, T: Context<'state, U>, U> Context<'state, U> for &T {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<U> {
(*self).get(iter)
}
}
impl<'state, T: Context<'state, U>, U> Context<'state, U> for Option<T> {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<U> {
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<Token<'a>> {
self.0.peek()
}
}
impl<'a> Iterator for TokenIter<'a> {
type Item = Token<'a>;
fn next (&mut self) -> Option<Token<'a>> {
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<SourceIter<'a>> 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<Token<'a>> {
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<Token<'a>> {
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<usize, ParseError> {
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<usize, ParseError> {
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
}
}

View file

@ -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<Self> {
None
}
fn try_from_atom <'source: 'state> (
state: &'state T, value: Value<'source>
) -> Option<Self> {
if let Exp(0, iter) = value {
return Self::try_from_expr(state, iter.clone())
}
None
}
}
pub trait TryIntoDsl<T>: Sized {
fn try_into_atom (&self) -> Option<Value>;
}
/// 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<U> {
None
}
}
impl<'state, T: Context<'state, U>, U> Context<'state, U> for &T {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<U> {
(*self).get(iter)
}
}
impl<'state, T: Context<'state, U>, U> Context<'state, U> for Option<T> {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<U> {
self.as_ref().map(|s|s.get(iter)).flatten()
}
}

View file

@ -1,15 +0,0 @@
use crate::*;
use thiserror::Error;
pub type ParseResult<T> = Result<T, ParseError>;
#[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),
}

View file

@ -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<Token<'a>> {
self.0.peek()
}
}
impl<'a> Iterator for TokenIter<'a> {
type Item = Token<'a>;
fn next (&mut self) -> Option<Token<'a>> {
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<SourceIter<'a>> 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<Token<'a>> {
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<Token<'a>> {
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<usize, ParseError> {
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<usize, ParseError> {
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))
})
}

View file

@ -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);
}
}
}

View file

@ -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
}
}

View file

@ -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::*;
@ -114,22 +143,6 @@ mod dsl_macros;
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");

View file

@ -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)
}
}
@ -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())
}
}

View file

@ -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<E: Engine> Handle<E> for $Struct {
impl<E: Engine> ::tengri::input::Handle<E> for $Struct {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$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
}
}

View file

@ -64,7 +64,6 @@ pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
#[cfg(test)] mod test {
use crate::*;
use proptest::{prelude::*, option::of};
use proptest::option::of;
proptest! {
#[test] fn proptest_direction (

View file

@ -1,7 +1,11 @@
use crate::*;
pub use Direction::*;
/// A split or layer.
pub struct Bsp<X, Y>(Direction, X, Y);
pub struct Bsp<A, B>(
pub(crate) Direction,
pub(crate) A,
pub(crate) B,
);
impl<E: Output, A: Content<E>, B: Content<E>> Content<E> for Bsp<A, B> {
fn layout (&self, outer: E::Area) -> E::Area {
let [_, _, c] = self.areas(outer);

View file

@ -22,18 +22,13 @@ impl<A, B> Either<A, B> {
try_from_expr!(<'source, 'state, E>: When<RenderBox<'state, E>>: |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<RenderBox<'state, E>>: |state, iter| {
try_from_expr!(<'source, 'state, E>: Either<RenderBox<'state, E>, 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)),
))
}
});

View file

@ -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<RenderBox<'state, E>> {
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<Self>
{
let mut iter = iter.clone();
@ -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),
@ -84,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<E::Unit, RenderBox<'state, E>> {
fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>) -> Option<Self> {
fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Option<Self> {
let mut iter = iter.clone();
if let Some(Token { value: Value::Key(k), .. }) = iter.peek() {
if k == $x || k == $y {

View file

@ -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<Self> {
let mut $iter = $iter.clone();
$body;

View file

@ -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};
@ -27,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 {
@ -54,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: ToTokens> (t: T) -> TokenStream {
let mut out = TokenStream2::new();
t.to_tokens(&mut out);
@ -73,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<SomeOutput> + use<'_> {
//"view-1"
//}
//}
//};
//let written = quote! { #parsed };
//assert_eq!(format!("{written}"), format!("{}", quote! {
//impl SomeView {
//fn view_1 (&self) -> impl Content<SomeOutput> + use<'_> {
//"view-1"
//}
//}
///// Generated by [tengri_proc].
//impl ::tengri::output::Content<SomeOutput> for SomeView {
//fn content (&self) -> impl Render<SomeOutput> {
//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<RenderBox<'a, SomeOutput>> {
//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<bool> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
////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<str>)] {
//":arcstr1" => "foo".into(),
//}
//#[tengri::expose(Option<Arc<str>>)] {
//":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<Arc<str>> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<Arc<str>> {
////Some(match dsl {
////::tengri::Value::Sym(":arcstr1") => "foo".into(),
////_ => return None
////})
////}
////}
////impl ::tengri::Context<Option<Arc<str>>> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<Option<Arc<str>>> {
////Some(match dsl {
////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()),
////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()),
////_ => return None
////})
////}
////}
////impl ::tengri::Context<bool> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
////Some(match dsl {
////::tengri::Value::Sym(":true") => true,
////::tengri::Value::Sym(":false") => false,
////::tengri::Value::Sym(":bool1") => true || false,
////_ => return None
////})
////}
////}
////impl ::tengri::Context<u16> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<u16> {
////Some(match dsl {
////::tengri::Value::Num(n) => *n as u16,
////::tengri::Value::Sym(":u161") => 0 + 1,
////_ => return None
////})
////}
////}
////impl ::tengri::Context<usize> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<usize> {
////Some(match dsl {
////::tengri::Value::Num(n) => *n as usize,
////::tengri::Value::Sym(":usize1") => 1 + 2,
////_ => return None
////})
////}
////}
////})
////)
//}

View file

@ -68,9 +68,9 @@ 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: TokenIter<'source>
state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source>
) -> Option<Self> {
let mut iter = iter.clone();
match iter.next() {
@ -80,7 +80,15 @@ impl ToTokens for CommandDef {
}
}
/// Generated by [tengri_proc].
impl Command<#target> for #enumeration {
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 {
fn execute (self, state: &mut #target) -> Perhaps<Self> {
match self {
#(#implementations)*
@ -121,13 +129,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,12 +155,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: Context::get(state, &mut iter).expect(#give_err),
#arg: ::tengri::dsl::Context::get(state, &mut iter).expect(#give_err),
});
}
out
@ -164,11 +171,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 +196,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::<Vec<_>>();
let give_rest = write_quote(quote! { /*TODO*/ });
let give_args = self.args()
.map(|(arg, _ty)|write_quote(quote! { #arg, }))
.collect::<Vec<_>>();
write_quote(quote! { Self::#variant => Self::#ident(state, #(#give_args)* #give_rest), })
}
}
@ -216,35 +225,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));
}
}

View file

@ -19,7 +19,7 @@ struct ExposeSym(LitStr);
struct ExposeType(Box<Type>);
impl Parse for ExposeMeta {
fn parse (input: ParseStream) -> Result<Self> {
fn parse (_input: ParseStream) -> Result<Self> {
Ok(Self)
}
}
@ -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<bool> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
////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<str>)] {
//":arcstr1" => "foo".into(),
//}
//#[tengri::expose(Option<Arc<str>>)] {
//":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<Arc<str>> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<Arc<str>> {
////Some(match dsl {
////::tengri::Value::Sym(":arcstr1") => "foo".into(),
////_ => return None
////})
////}
////}
////impl ::tengri::Context<Option<Arc<str>>> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<Option<Arc<str>>> {
////Some(match dsl {
////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()),
////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()),
////_ => return None
////})
////}
////}
////impl ::tengri::Context<bool> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<bool> {
////Some(match dsl {
////::tengri::Value::Sym(":true") => true,
////::tengri::Value::Sym(":false") => false,
////::tengri::Value::Sym(":bool1") => true || false,
////_ => return None
////})
////}
////}
////impl ::tengri::Context<u16> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<u16> {
////Some(match dsl {
////::tengri::Value::Num(n) => *n as u16,
////::tengri::Value::Sym(":u161") => 0 + 1,
////_ => return None
////})
////}
////}
////impl ::tengri::Context<usize> for Something {
////fn get (&self, dsl: &::tengri::Value) -> Option<usize> {
////Some(match dsl {
////::tengri::Value::Num(n) => *n as usize,
////::tengri::Value::Sym(":usize1") => 1 + 2,
////_ => return None
////})
////}
////}
////})
////)
}

View file

@ -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)]
@ -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<RenderBox<'state, #output>>
{
match value {
#(#exposed)*
_ => panic!("expected Sym(content), got: {value:?}")
}
match value { #(#exposed)* _ => panic!(#error_msg) }
}
}
} {
@ -108,132 +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));
//}
//}
fn nth_segment_is (segments: &Punctuated<PathSegment, PathSep>, 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<SomeOutput> + use<'_> {
//"view-1"
//}
//}
//};
//let written = quote! { #parsed };
//assert_eq!(format!("{written}"), format!("{}", quote! {
//impl SomeView {
//fn view_1 (&self) -> impl Content<SomeOutput> + use<'_> {
//"view-1"
//}
//}
///// Generated by [tengri_proc].
//impl ::tengri::output::Content<SomeOutput> for SomeView {
//fn content (&self) -> impl Render<SomeOutput> {
//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<RenderBox<'a, SomeOutput>> {
//match value {
//::tengri::dsl::Value::Sym(":view-1") => self.view_1().boxed(),
//_ => panic!("expected Sym(content), got: {value:?}")
//}
//}
//}
//}));
}

View file

@ -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" ]

View file

@ -2,3 +2,98 @@
#[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<Self> {
Ok(None)
}
fn do_thing_arg (state: &mut Test, arg: usize) -> Perhaps<Self> {
Ok(None)
}
fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps<Self> {
Ok(command.execute(state)?.map(|command|Self::DoSub { command }))
}
}
#[tengri_proc::command(Test)] impl TestSubcommand {
fn do_other_thing (state: &mut Test) -> Perhaps<Self> {
Ok(None)
}
fn do_other_thing_arg (state: &mut Test, arg: usize) -> Perhaps<Self> {
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));
}

View file

@ -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] => {}