edn -> dsl

This commit is contained in:
🪞👃🪞 2025-03-14 23:44:13 +02:00
parent d30eda33d1
commit 877b344765
35 changed files with 197 additions and 225 deletions

130
dsl/src/context.rs Normal file
View file

@ -0,0 +1,130 @@
use crate::*;
pub trait TryFromAtom<'a, T>: Sized {
fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option<Self> { None }
fn try_from_atom (state: &'a T, value: Value<'a>) -> Option<Self> {
if let Exp(0, iter) = value { return Self::try_from_expr(state, iter) }
None
}
}
pub trait TryIntoAtom<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<U>: Sized {
fn get (&self, _atom: &Value) -> Option<U> {
None
}
fn get_or_fail (&self, atom: &Value) -> U {
self.get(atom).expect("no value")
}
}
impl<T: Context<U>, U> Context<U> for &T {
fn get (&self, atom: &Value) -> Option<U> {
(*self).get(atom)
}
fn get_or_fail (&self, atom: &Value) -> U {
(*self).get_or_fail(atom)
}
}
impl<T: Context<U>, U> Context<U> for Option<T> {
fn get (&self, atom: &Value) -> Option<U> {
self.as_ref().map(|s|s.get(atom)).flatten()
}
fn get_or_fail (&self, atom: &Value) -> U {
self.as_ref().map(|s|s.get_or_fail(atom)).expect("no provider")
}
}
/// Implement `Context` for a context and type.
#[macro_export] macro_rules! provide {
// Provide a value to the EDN template
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl Context<$type> for $State {
#[allow(unreachable_code)]
fn get (&$self, atom: &Value) -> Option<$type> {
use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* _ => return None })
}
}
};
// Provide a value more generically
($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl<$lt> Context<$lt, $type> for $State {
#[allow(unreachable_code)]
fn get (&$lt $self, atom: &Value) -> Option<$type> {
use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* _ => return None })
}
}
};
}
/// Implement `Context` for a context and numeric type.
///
/// This enables support for numeric literals.
#[macro_export] macro_rules! provide_num {
// Provide a value that may also be a numeric literal in the EDN, to a generic implementation.
($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl<$T: $Trait> Context<$type> for $T {
fn get (&$self, atom: &Value) -> Option<$type> {
use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None })
}
}
};
// Provide a value that may also be a numeric literal in the EDN, to a concrete implementation.
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl Context<$type> for $State {
fn get (&$self, atom: &Value) -> Option<$type> {
use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None })
}
}
};
}
/// Implement `Context` for a context and the boolean type.
///
/// This enables support for boolean literals.
#[macro_export] macro_rules! provide_bool {
// Provide a value that may also be a numeric literal in the EDN, to a generic implementation.
($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl<$T: $Trait> Context<$type> for $T {
fn get (&$self, atom: &Value) -> Option<$type> {
use Value::*;
Some(match atom {
Num(n) => match *n { 0 => false, _ => true },
Sym(":false") | Sym(":f") => false,
Sym(":true") | Sym(":t") => true,
$(Sym($pat) => $expr,)*
_ => return Context::get(self, atom)
})
}
}
};
// Provide a value that may also be a numeric literal in the EDN, to a concrete implementation.
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl Context<$type> for $State {
fn get (&$self, atom: &Value) -> Option<$type> {
use Value::*;
Some(match atom {
Num(n) => match *n { 0 => false, _ => true },
Sym(":false") | Sym(":f") => false,
Sym(":true") | Sym(":t") => true,
$(Sym($pat) => $expr,)*
_ => return None
})
}
}
};
}
#[cfg(test)] #[test] fn test_edn_context () {
struct Test;
provide_bool!(bool: |self: Test|{
":provide-bool" => true
});
let test = Test;
assert_eq!(test.get(&Value::Sym(":false")), Some(false));
assert_eq!(test.get(&Value::Sym(":true")), Some(true));
assert_eq!(test.get(&Value::Sym(":provide-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));
}

15
dsl/src/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),
}

144
dsl/src/iter.rs Normal file
View file

@ -0,0 +1,144 @@
//! 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})
}
}
/// 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, 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))
},
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))
})
}
#[cfg(test)] mod test_token_iter {
use super::*;
//use proptest::prelude::*;
#[test] fn test_iters () {
let mut iter = crate::SourceIter::new(&":foo :bar");
let _ = iter.next();
let mut iter = crate::TokenIter::new(&":foo :bar");
let _ = iter.next();
}
#[test] const fn test_const_iters () {
let mut iter = crate::SourceIter::new(&":foo :bar");
let _ = iter.next();
}
#[test] fn test_num () {
let digit = to_digit('0');
let digit = to_digit('x');
let number = to_number(&"123");
let number = to_number(&"12asdf3");
}
//proptest! {
//#[test] fn proptest_source_iter (
//source in "\\PC*"
//) {
//let mut iter = crate::SourceIter::new(&source);
////let _ = iter.next();
//}
//#[test] fn proptest_token_iter (
//source in "\\PC*"
//) {
//let mut iter = crate::TokenIter::new(&source);
////let _ = iter.next();
//}
//}
}

60
dsl/src/lib.rs Normal file
View file

@ -0,0 +1,60 @@
#![feature(adt_const_params)]
#![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_fn_trait_return)]
mod error; pub use self::error::*;
mod token; pub use self::token::*;
mod iter; pub use self::iter::*;
mod context; pub use self::context::*;
pub(crate) use self::Value::*;
pub(crate) use self::ParseError::*;
pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind};
pub(crate) use konst::string::{split_at, str_range, char_indices};
pub(crate) use std::fmt::Debug;
/// 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;
}
}
}
//#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> {
//// Let's pretend to render some view.
//let source = include_str!("../../tek/src/view_arranger.edn");
//// The token iterator allows you to get the tokens represented by the source text.
//let mut view = TokenIter(source);
//// The token iterator wraps a const token+source iterator.
//assert_eq!(view.0.0, source);
//let mut expr = view.peek();
//assert_eq!(view.0.0, source);
//assert_eq!(expr, Some(Token {
//source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter(&source[1..]))
//}));
////panic!("{view:?}");
////panic!("{:#?}", expr);
////for example in [
////include_str!("../../tui/examples/edn01.edn"),
////include_str!("../../tui/examples/edn02.edn"),
////] {
//////let items = Atom::read_all(example)?;
//////panic!("{layout:?}");
//////let content = <dyn ViewContext<::tengri_engine::tui::Tui>>::from(&layout);
////}
//Ok(())
//}

169
dsl/src/token.rs Normal file
View file

@ -0,0 +1,169 @@
//! [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::*;
use self::Value::*;
#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'a> {
pub source: &'a str,
pub start: usize,
pub length: usize,
pub value: Value<'a>,
}
#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'a> {
#[default] Nil,
Err(ParseError),
Num(usize),
Sym(&'a str),
Key(&'a str),
Exp(usize, TokenIter<'a>),
}
impl<'a> Token<'a> {
pub const fn new (source: &'a str, start: usize, length: usize, value: Value<'a>) -> Self {
Self { source, start, length, value }
}
pub const fn end (&self) -> usize {
self.start.saturating_add(self.length)
}
pub const fn slice (&'a self) -> &'a str {
self.slice_source(self.source)
//str_range(self.source, self.start, self.end())
}
pub const fn slice_source <'b> (&'a self, source: &'b str) -> &'b str {
str_range(source, self.start, self.end())
}
pub const fn slice_source_exp <'b> (&'a self, source: &'b str) -> &'b 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_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
}
}
#[cfg(test)] mod test_token_prop {
use proptest::prelude::*;
proptest! {
#[test] fn test_token_prop (
source in "\\PC*",
start in usize::MIN..usize::MAX,
length in usize::MIN..usize::MAX,
) {
let token = crate::Token {
source: &source,
start,
length,
value: crate::Value::Nil
};
let _ = token.slice();
}
}
}
#[cfg(test)] #[test] fn test_token () -> Result<(), Box<dyn std::error::Error>> {
let source = ":f00";
let mut token = Token { source, start: 0, length: 1, value: Sym(":") };
token = token.grow_sym();
assert_eq!(token, Token { source, start: 0, length: 2, value: Sym(":f") });
token = token.grow_sym();
assert_eq!(token, Token { source, start: 0, length: 3, value: Sym(":f0") });
token = token.grow_sym();
assert_eq!(token, Token { source, start: 0, length: 4, value: Sym(":f00") });
let src = "";
assert_eq!(None, SourceIter(src).next());
let src = " \n \r \t ";
assert_eq!(None, SourceIter(src).next());
let src = "7";
assert_eq!(Num(7), SourceIter(src).next().unwrap().0.value);
let src = " 100 ";
assert_eq!(Num(100), SourceIter(src).next().unwrap().0.value);
let src = " 9a ";
assert_eq!(Err(Unexpected('a')), SourceIter(src).next().unwrap().0.value);
let src = " :123foo ";
assert_eq!(Sym(":123foo"), SourceIter(src).next().unwrap().0.value);
let src = " \r\r\r\n\n\n@bar456\t\t\t\t\t\t";
assert_eq!(Sym("@bar456"), SourceIter(src).next().unwrap().0.value);
let src = "foo123";
assert_eq!(Key("foo123"), SourceIter(src).next().unwrap().0.value);
let src = "foo/bar";
assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value);
Ok(())
}