Compare commits

..

No commits in common. "cb8fd26922fd1cfad4ceadeb89e48544531a178e" and "5e09f5a4bb386ab9cb3a80f4e74ac521a817a4e8" have entirely different histories.

22 changed files with 747 additions and 718 deletions

3
Cargo.lock generated
View file

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

View file

@ -1,320 +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),
}
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
}
}

40
dsl/src/dsl_context.rs Normal file
View file

@ -0,0 +1,40 @@
use crate::*;
pub trait TryFromDsl<'state, T>: Sized {
fn try_from_expr <'source: 'state> (
_state: &'state T, _iter: TokenIter<'source>
) -> Option<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()
}
}

15
dsl/src/dsl_error.rs Normal file
View file

@ -0,0 +1,15 @@
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),
}

157
dsl/src/dsl_iter.rs Normal file
View file

@ -0,0 +1,157 @@
//! The token iterator [TokenIter] allows you to get the
//! general-purpose syntactic [Token]s represented by the source text.
//!
//! Both iterators are `peek`able:
//!
//! ```
//! let src = include_str!("../test.edn");
//! let mut view = tengri_dsl::TokenIter::new(src);
//! assert_eq!(view.0.0, src);
//! assert_eq!(view.peek(), view.0.peek())
//! ```
use crate::*;
/// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter]
/// [TokenIter::next] returns just the [Token] and mutates `self`,
/// instead of returning an updated version of the struct as [SourceIter::next] does.
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct TokenIter<'a>(
pub SourceIter<'a>
);
impl<'a> TokenIter<'a> {
pub const fn new (source: &'a str) -> Self {
Self(SourceIter::new(source))
}
pub const fn peek (&self) -> Option<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))
})
}

46
dsl/src/dsl_macros.rs Normal file
View file

@ -0,0 +1,46 @@
/// Static iteration helper.
#[macro_export] macro_rules! iterate {
($expr:expr => $arg: pat => $body:expr) => {
let mut iter = $expr;
while let Some(($arg, next)) = iter.next() {
$body;
iter = next;
}
}
}
/// Implement the const iterator pattern.
#[macro_export] macro_rules! const_iter {
($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => {
impl$(<$l>)? Iterator for $Struct {
type Item = $Item;
fn next (&mut $self) -> Option<$Item> { $expr }
}
impl$(<$l>)? ConstIntoIter for $Struct {
type Kind = IsIteratorKind;
type Item = $Item;
type IntoIter = Self;
}
}
}
#[macro_export] macro_rules! get_value {
($state:expr => $token:expr) => {
if let Some(value) = $state.get(&$token.value) {
value
} else {
panic!("no value corresponding to {:?}", &$token.value);
}
}
}
#[macro_export] macro_rules! get_content {
($state:expr => $token:expr) => {
if let Some(content) = $state.get_content(&$token.value) {
content
} else {
panic!("no content corresponding to {:?}", &$token.value);
}
}
}

120
dsl/src/dsl_token.rs Normal file
View file

@ -0,0 +1,120 @@
//! [Token]s are parsed substrings with an associated [Value].
//!
//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err]
//! * [Value::Exp] wraps an expression depth and a [SourceIter]
//! with the remaining part of the expression.
//! * expression depth other that 0 mean unclosed parenthesis.
//! * closing and unopened parenthesis panics during reading.
//! * [ ] TODO: signed depth might be interesting
//! * [Value::Sym] and [Value::Key] are stringish literals
//! with slightly different parsing rules.
//! * [Value::Num] is an unsigned integer literal.
//!```
//! use tengri_dsl::{*, Value::*};
//! let source = include_str!("../test.edn");
//! let mut view = TokenIter::new(source);
//! assert_eq!(view.peek(), Some(Token {
//! source,
//! start: 0,
//! length: source.len(),
//! value: Exp(0, TokenIter::new(&source[1..]))
//! }));
//!```
use crate::*;
#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> {
pub source: &'source str,
pub start: usize,
pub length: usize,
pub value: Value<'source>,
}
#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> {
#[default] Nil,
Err(ParseError),
Num(usize),
Sym(&'source str),
Key(&'source str),
Str(&'source str),
Exp(usize, TokenIter<'source>),
}
impl<'source> Token<'source> {
pub const fn new (source: &'source str, start: usize, length: usize, value: Value<'source>) -> Self {
Self { source, start, length, value }
}
pub const fn end (&self) -> usize {
self.start.saturating_add(self.length)
}
pub const fn slice (&'source self) -> &'source str {
self.slice_source(self.source)
//str_range(self.source, self.start, self.end())
}
pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str {
str_range(source, self.start, self.end())
}
pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str {
str_range(source, self.start.saturating_add(1), self.end())
}
pub const fn value (&self) -> Value {
self.value
}
pub const fn error (self, error: ParseError) -> Self {
Self { value: Value::Err(error), ..self }
}
pub const fn grow (self) -> Self {
Self { length: self.length.saturating_add(1), ..self }
}
pub const fn grow_num (self, m: usize, c: char) -> Self {
match to_digit(c) {
Ok(n) => Self { value: Num(10*m+n), ..self.grow() },
Result::Err(e) => Self { value: Err(e), ..self.grow() },
}
}
pub const fn grow_key (self) -> Self {
let mut token = self.grow();
token.value = Key(token.slice_source(self.source));
token
}
pub const fn grow_sym (self) -> Self {
let mut token = self.grow();
token.value = Sym(token.slice_source(self.source));
token
}
pub const fn grow_str (self) -> Self {
let mut token = self.grow();
token.value = Str(token.slice_source(self.source));
token
}
pub const fn grow_exp (self) -> Self {
let mut token = self.grow();
if let Exp(depth, _) = token.value {
token.value = Exp(depth, TokenIter::new(token.slice_source_exp(self.source)));
} else {
unreachable!()
}
token
}
pub const fn grow_in (self) -> Self {
let mut token = self.grow_exp();
if let Value::Exp(depth, source) = token.value {
token.value = Value::Exp(depth.saturating_add(1), source)
} else {
unreachable!()
}
token
}
pub const fn grow_out (self) -> Self {
let mut token = self.grow_exp();
if let Value::Exp(depth, source) = token.value {
if depth > 0 {
token.value = Value::Exp(depth - 1, source)
} else {
return self.error(Unexpected(')'))
}
} else {
unreachable!()
}
token
}
}

View file

@ -1,36 +1,3 @@
//! [Token]s are parsed substrings with an associated [Value].
//!
//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err]
//! * [Value::Exp] wraps an expression depth and a [SourceIter]
//! with the remaining part of the expression.
//! * expression depth other that 0 mean unclosed parenthesis.
//! * closing and unopened parenthesis panics during reading.
//! * [ ] TODO: signed depth might be interesting
//! * [Value::Sym] and [Value::Key] are stringish literals
//! with slightly different parsing rules.
//! * [Value::Num] is an unsigned integer literal.
//!```
//! use tengri_dsl::{*, Value::*};
//! let source = include_str!("../test.edn");
//! let mut view = TokenIter::new(source);
//! assert_eq!(view.peek(), Some(Token {
//! source,
//! start: 0,
//! length: source.len(),
//! value: Exp(0, TokenIter::new(&source[1..]))
//! }));
//!```
//! The token iterator [TokenIter] allows you to get the
//! general-purpose syntactic [Token]s represented by the source text.
//!
//! Both iterators are `peek`able:
//!
//! ```
//! let src = include_str!("../test.edn");
//! let mut view = tengri_dsl::TokenIter::new(src);
//! assert_eq!(view.0.0, src);
//! assert_eq!(view.peek(), view.0.peek())
//! ```
#![feature(adt_const_params)] #![feature(adt_const_params)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_fn_trait_return)] #![feature(impl_trait_in_fn_trait_return)]
@ -41,7 +8,11 @@ pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind};
pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use konst::string::{split_at, str_range, char_indices};
pub(crate) use std::fmt::Debug; pub(crate) use std::fmt::Debug;
mod dsl; pub use self::dsl::*; mod dsl_error; pub use self::dsl_error::*;
mod dsl_token; pub use self::dsl_token::*;
mod dsl_iter; pub use self::dsl_iter::*;
mod dsl_context; pub use self::dsl_context::*;
mod dsl_macros;
#[cfg(test)] mod test_token_iter { #[cfg(test)] mod test_token_iter {
use crate::*; use crate::*;
@ -143,6 +114,22 @@ mod dsl; pub use self::dsl::*;
Ok(()) 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> { //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> {
//// Let's pretend to render some view. //// Let's pretend to render some view.
//let source = include_str!("../../tek/src/view_arranger.edn"); //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() { match exp_iter.next() {
Some(Token { value: Value::Sym(binding), .. }) => { Some(Token { value: Value::Sym(binding), .. }) => {
if input.matches_dsl(binding) { if input.matches_dsl(binding) {
if let Some(command) = C::try_from_expr(state, &mut exp_iter) { if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
return Some(command) 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() { match exp_iter.next() {
Some(Token { value: Value::Sym(binding), .. }) => { Some(Token { value: Value::Sym(binding), .. }) => {
if input.matches_dsl(binding) { if input.matches_dsl(binding) {
if let Some(command) = C::try_from_expr(state, &mut exp_iter) { if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
return Some(command) return Some(command)
} }
} }
@ -132,7 +132,8 @@ where
M: KeyMap<'state, S, C, I> + Send + Sync M: KeyMap<'state, S, C, I> + Send + Sync
{ {
fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "[InputMap: {} layer(s)]", self.layers.len()) write!(f, "[InputMap: {} layer(s)]", self.layers.len());
Ok(())
} }
} }

View file

@ -18,17 +18,15 @@ pub trait Input: Send + Sync + Sized {
/// Implement the [Handle] trait. /// Implement the [Handle] trait.
#[macro_export] macro_rules! handle { #[macro_export] macro_rules! handle {
(|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { (|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl<E: Engine> ::tengri::input::Handle<E> for $Struct { impl<E: Engine> Handle<E> for $Struct {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> { fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler $handler
} }
} }
}; };
($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => { ($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl ::tengri::input::Handle<$E> for $Struct { impl Handle<$E> for $Struct {
fn handle (&mut $self, $input: &$E) -> fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> {
Perhaps<<$E as ::tengri::input::Input>::Handled>
{
$handler $handler
} }
} }

View file

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

View file

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

View file

@ -22,13 +22,18 @@ impl<A, B> Either<A, B> {
try_from_expr!(<'source, 'state, E>: When<RenderBox<'state, E>>: |state, iter| { try_from_expr!(<'source, 'state, E>: When<RenderBox<'state, E>>: |state, iter| {
if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() { if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() {
let _ = iter.next().unwrap(); let _ = iter.next().unwrap();
let content = iter.next().expect("no content specified").value;
return Some(Self( let condition = iter.next().expect("no condition specified");
state.get(&mut iter) let condition = state.get(&mut iter).expect("no condition provided");
.expect("no condition provided"),
state.get_content(&content) let content = iter.next().expect("no content specified");
.unwrap_or_else(||panic!("no content corresponding to for {:?}", &content)) let content = if let Some(content) = state.get_content(&content.value) {
)) content
} else {
panic!("no content corresponding to for {:?}", &content);
};
return Some(Self(condition, content))
} }
}); });
@ -36,16 +41,25 @@ try_from_expr!(<'source, 'state, E>: When<RenderBox<'state, E>>: |state, iter| {
try_from_expr!(<'source, 'state, E>: Either<RenderBox<'state, E>, 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() { if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() {
let _ = iter.next().unwrap(); let _ = iter.next().unwrap();
let content = iter.next().expect("no content specified").value;
let alternate = iter.next().expect("no alternate specified").value; let condition = iter.next().expect("no condition specified");
return Some(Self( let condition = state.get(&mut iter).expect("no condition provided");
state.get(&mut iter)
.expect("no condition provided"), let content = iter.next().expect("no content specified");
state.get_content(&content) let content = if let Some(content) = state.get_content(&content.value) {
.unwrap_or_else(||panic!("no content 1 corresponding to {:?}", &content)), content
state.get_content(&alternate) } else {
.unwrap_or_else(||panic!("no content 2 corresponding to {:?}", &alternate)), 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))
} }
}); });

View file

@ -35,7 +35,7 @@ macro_rules! transform_xy {
#[cfg(feature = "dsl")] #[cfg(feature = "dsl")]
impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T>
for $Enum<RenderBox<'state, E>> { for $Enum<RenderBox<'state, E>> {
fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>)
-> Option<Self> -> Option<Self>
{ {
let mut iter = iter.clone(); let mut iter = iter.clone();
@ -43,11 +43,7 @@ macro_rules! transform_xy {
if k == $x || k == $y || k == $xy { if k == $x || k == $y || k == $xy {
let _ = iter.next().unwrap(); let _ = iter.next().unwrap();
let token = iter.next().expect("no content specified"); let token = iter.next().expect("no content specified");
let content = if let Some(content) = state.get_content(&token.value) { let content = get_content!(state => token);
content
} else {
panic!("no content corresponding to {:?}", &token.value);
};
return Some(match k { return Some(match k {
$x => Self::x(content), $x => Self::x(content),
$y => Self::y(content), $y => Self::y(content),
@ -88,7 +84,7 @@ macro_rules! transform_xy_unit {
#[cfg(feature = "dsl")] #[cfg(feature = "dsl")]
impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T>
for $Enum<E::Unit, RenderBox<'state, E>> { for $Enum<E::Unit, RenderBox<'state, E>> {
fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Option<Self> { fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>) -> Option<Self> {
let mut iter = iter.clone(); let mut iter = iter.clone();
if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() {
if k == $x || k == $y { 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 { > TryFromDsl<$lt_state, T> for $Struct {
fn try_from_expr <$lt_source: $lt_state> ( fn try_from_expr <$lt_source: $lt_state> (
$state: &$lt_state T, $state: &$lt_state T,
$iter: &mut TokenIter<$lt_source> $iter: TokenIter<$lt_source>
) -> Option<Self> { ) -> Option<Self> {
let mut $iter = $iter.clone(); let mut $iter = $iter.clone();
$body; $body;

View file

@ -2,17 +2,23 @@
#![feature(box_patterns)] #![feature(box_patterns)]
extern crate proc_macro; extern crate proc_macro;
pub(crate) use std::collections::BTreeMap; pub(crate) use std::collections::{BTreeMap, BTreeSet};
pub(crate) use std::cmp::Ordering; pub(crate) use std::cmp::Ordering;
pub(crate) use std::sync::Arc; pub(crate) use std::sync::Arc;
pub(crate) use proc_macro::TokenStream; pub(crate) use proc_macro::TokenStream;
pub(crate) use proc_macro2::{ pub(crate) use proc_macro2::{
TokenStream as TokenStream2, Ident, Span, Punct, Group, Delimiter, Spacing::* TokenStream as TokenStream2, TokenTree,
Ident, Span, Punct, Spacing::*, Group, Delimiter, Literal
}; };
pub(crate) use syn::{ pub(crate) use syn::{
parse_macro_input, ImplItem, ImplItemFn, LitStr, Type, parse, parse_macro_input, parse_quote as pq,
ItemImpl, ReturnType, Signature, FnArg, Pat, PatType, PatIdent, braced, bracketed, parenthesized, Token,
Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments,
ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, FnArg,
Pat, PatType, PatIdent,
parse::{Parse, ParseStream, Result}, parse::{Parse, ParseStream, Result},
token::{PathSep, Brace},
punctuated::Punctuated,
}; };
pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; pub(crate) use quote::{quote, TokenStreamExt, ToTokens};
pub(crate) use heck::{AsKebabCase, AsUpperCamelCase}; pub(crate) use heck::{AsKebabCase, AsUpperCamelCase};
@ -21,7 +27,14 @@ mod proc_view;
mod proc_expose; mod proc_expose;
mod proc_command; mod proc_command;
#[cfg(test)] use syn::parse_quote as pq; #[proc_macro_attribute]
pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream {
use self::proc_view::{ViewDef, ViewMeta, ViewImpl};
write(ViewDef(
parse_macro_input!(meta as ViewMeta),
parse_macro_input!(item as ViewImpl),
))
}
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream {
@ -41,15 +54,6 @@ pub fn command (meta: TokenStream, item: TokenStream) -> TokenStream {
)) ))
} }
#[proc_macro_attribute]
pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream {
use self::proc_view::{ViewDef, ViewMeta, ViewImpl};
write(ViewDef(
parse_macro_input!(meta as ViewMeta),
parse_macro_input!(item as ViewImpl),
))
}
pub(crate) fn write <T: ToTokens> (t: T) -> TokenStream { pub(crate) fn write <T: ToTokens> (t: T) -> TokenStream {
let mut out = TokenStream2::new(); let mut out = TokenStream2::new();
t.to_tokens(&mut out); t.to_tokens(&mut out);
@ -69,175 +73,3 @@ pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) {
out.append(token); 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 #block
/// Generated by [tengri_proc]. /// Generated by [tengri_proc].
impl<'state> ::tengri::dsl::TryFromDsl<'state, #target> for #enumeration { impl<'state> TryFromDsl<'state, #target> for #enumeration {
fn try_from_expr <'source: 'state> ( fn try_from_expr <'source: 'state> (
state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> state: &'state #target, iter: TokenIter<'source>
) -> Option<Self> { ) -> Option<Self> {
let mut iter = iter.clone(); let mut iter = iter.clone();
match iter.next() { match iter.next() {
@ -80,15 +80,7 @@ impl ToTokens for CommandDef {
} }
} }
/// Generated by [tengri_proc]. /// Generated by [tengri_proc].
impl<'state> ::tengri::dsl::Context<'state, #enumeration> for #target { impl Command<#target> for #enumeration {
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> { fn execute (self, state: &mut #target) -> Perhaps<Self> {
match self { match self {
#(#implementations)* #(#implementations)*
@ -129,12 +121,13 @@ impl CommandArm {
Some((arg, ty)) Some((arg, ty))
} else { } else {
unreachable!("only typed args should be present at this position"); unreachable!("only typed args should be present at this position");
None
}) })
} }
fn to_enum_variant_def (&self) -> TokenStream2 { fn to_enum_variant_def (&self) -> TokenStream2 {
let mut out = TokenStream2::new(); let mut out = TokenStream2::new();
out.append(self.to_enum_variant_ident()); out.append(self.to_enum_variant_ident());
//let ident = &self.0; let ident = &self.0;
if self.has_args() { if self.has_args() {
out.append(Group::new(Delimiter::Brace, { out.append(Group::new(Delimiter::Brace, {
let mut out = TokenStream2::new(); let mut out = TokenStream2::new();
@ -155,12 +148,12 @@ impl CommandArm {
out.append(Group::new(Delimiter::Brace, { out.append(Group::new(Delimiter::Brace, {
let mut out = TokenStream2::new(); let mut out = TokenStream2::new();
for (arg, ty) in self.args() { for (arg, ty) in self.args() {
//let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})",
//quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site());
let give_err = LitStr::new(&format!("{}: missing value for \"{}\" ({})", let give_err = LitStr::new(&format!("{}: missing value for \"{}\" ({})",
quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site());
write_quote_to(&mut out, quote! { write_quote_to(&mut out, quote! {
#arg: ::tengri::dsl::Context::get(state, &mut iter).expect(#give_err), #arg: Context::get(state, &mut iter).expect(#give_err),
}); });
} }
out out
@ -171,11 +164,11 @@ impl CommandArm {
fn to_enum_variant_unbind (&self) -> TokenStream2 { fn to_enum_variant_unbind (&self) -> TokenStream2 {
let mut out = TokenStream2::new(); let mut out = TokenStream2::new();
out.append(self.to_enum_variant_ident()); out.append(self.to_enum_variant_ident());
//let ident = &self.0; let ident = &self.0;
if self.has_args() { if self.has_args() {
out.append(Group::new(Delimiter::Brace, { out.append(Group::new(Delimiter::Brace, {
let mut out = TokenStream2::new(); let mut out = TokenStream2::new();
for (arg, _ty) in self.args() { for (arg, ty) in self.args() {
write_quote_to(&mut out, quote! { #arg , }); write_quote_to(&mut out, quote! { #arg , });
} }
out out
@ -196,10 +189,8 @@ impl CommandArm {
fn to_implementation (&self) -> TokenStream2 { fn to_implementation (&self) -> TokenStream2 {
let ident = &self.0; let ident = &self.0;
let variant = self.to_enum_variant_unbind(); let variant = self.to_enum_variant_unbind();
let give_rest = write_quote(quote! { /*TODO*/ }); let mut give_rest = write_quote(quote! { /*TODO*/ });
let give_args = self.args() let give_args = self.args().map(|(arg, ty)|write_quote(quote! { #arg, })).collect::<Vec<_>>();
.map(|(arg, _ty)|write_quote(quote! { #arg, }))
.collect::<Vec<_>>();
write_quote(quote! { Self::#variant => Self::#ident(state, #(#give_args)* #give_rest), }) write_quote(quote! { Self::#variant => Self::#ident(state, #(#give_args)* #give_rest), })
} }
} }
@ -225,3 +216,35 @@ impl ToTokens for CommandVariant {
out.append(Punct::new(',', Alone)); 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>); struct ExposeType(Box<Type>);
impl Parse for ExposeMeta { impl Parse for ExposeMeta {
fn parse (_input: ParseStream) -> Result<Self> { fn parse (input: ParseStream) -> Result<Self> {
Ok(Self) Ok(Self)
} }
} }
@ -189,3 +189,109 @@ impl ToTokens for ExposeType {
self.0.to_tokens(out) 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)] #[derive(Debug, Clone)]
pub(crate) struct ViewMeta { pub(crate) struct ViewMeta {
pub(crate) output: Ident, output: Ident,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -45,16 +45,7 @@ impl ToTokens for ViewDef {
fn to_tokens (&self, out: &mut TokenStream2) { fn to_tokens (&self, out: &mut TokenStream2) {
let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self;
let ident = &block.self_ty; let ident = &block.self_ty;
let mut available = vec![]; let exposed: Vec<_> = exposed.iter().map(|(k,v)|ViewArm(k.clone(), v.clone())).collect();
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! { for token in quote! {
#block #block
/// Generated by [tengri_proc]. /// Generated by [tengri_proc].
@ -68,7 +59,10 @@ impl ToTokens for ViewDef {
fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>)
-> Option<RenderBox<'state, #output>> -> Option<RenderBox<'state, #output>>
{ {
match value { #(#exposed)* _ => panic!(#error_msg) } match value {
#(#exposed)*
_ => panic!("expected Sym(content), got: {value:?}")
}
} }
} }
} { } {
@ -114,3 +108,132 @@ impl ToTokens for ViewArm {
out.append(Punct::new(',', Alone)); 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,11 +10,6 @@ tengri_input = { optional = true, path = "../input" }
tengri_output = { optional = true, path = "../output" } tengri_output = { optional = true, path = "../output" }
tengri_tui = { optional = true, path = "../tui" } tengri_tui = { optional = true, path = "../tui" }
[dev-dependencies]
tengri_proc = { path = "../proc" }
tengri = { path = ".", features = [ "dsl" ] }
crossterm = "0.28.1"
[features] [features]
default = [ "input", "output", "tui" ] default = [ "input", "output", "tui" ]
input = [ "tengri_input" ] input = [ "tengri_input" ]

View file

@ -2,98 +2,3 @@
#[cfg(feature="input")] pub use ::tengri_input as input; #[cfg(feature="input")] pub use ::tengri_input as input;
#[cfg(feature="dsl")] pub use ::tengri_dsl as dsl; #[cfg(feature="dsl")] pub use ::tengri_dsl as dsl;
#[cfg(feature="tui")] pub use ::tengri_tui as tui; #[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,9 +43,6 @@ handle!(TuiIn: |self: Example, input|{
#[tengri_proc::expose] #[tengri_proc::expose]
impl Example { impl Example {
fn _todo_u16_stub (&self) -> u16 { todo!() }
fn _todo_bool_stub (&self) -> bool { todo!() }
fn _todo_usize_stub (&self) -> usize { todo!() }
//[bool] => {} //[bool] => {}
//[u16] => {} //[u16] => {}
//[usize] => {} //[usize] => {}