From e72225f83c89a2bc9c6bee9b00373dfa530e1909 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 16 Jul 2025 00:10:03 +0300 Subject: [PATCH] wip: fix dsl --- Cargo.toml | 1 + dsl/Cargo.toml | 1 + dsl/src/ast.rs | 38 ---- dsl/src/cst.rs | 312 -------------------------------- dsl/src/dsl.rs | 178 ------------------- dsl/src/lib.rs | 371 ++++++++++++++++++++++++++++++++++++++- dsl/src/test.rs | 18 +- output/src/ops.rs | 8 +- proc/src/proc_command.rs | 6 +- proc/src/proc_expose.rs | 5 +- proc/src/proc_view.rs | 57 +++--- 11 files changed, 421 insertions(+), 574 deletions(-) delete mode 100644 dsl/src/ast.rs delete mode 100644 dsl/src/cst.rs delete mode 100644 dsl/src/dsl.rs diff --git a/Cargo.toml b/Cargo.toml index a7bb2eb..fa89dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ edition = "2024" [workspace.dependencies] atomic_float = { version = "1" } better-panic = { version = "0.3.0" } +const_panic = { version = "0.2.12", features = [ "derive" ] } crossterm = { version = "0.28.1" } heck = { version = "0.5" } itertools = { version = "0.14.0" } diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index ab0bc37..256b599 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -7,6 +7,7 @@ edition = { workspace = true } [dependencies] tengri_core = { path = "../core" } konst = { workspace = true } +const_panic = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } diff --git a/dsl/src/ast.rs b/dsl/src/ast.rs deleted file mode 100644 index ac85d87..0000000 --- a/dsl/src/ast.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! The abstract syntax tree (AST) can be produced from the CST -//! by cloning source slices into owned ([Arc]) string slices. -use crate::*; - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(pub Arc, Ast>>>); - -impl Dsl for Ast { - type Str = Arc; - type Exp = Ast; - fn nth (&self, index: usize) -> Option> { - self.0.get(index).cloned() - } -} - -impl<'s> From> for Ast { - fn from (cst: Cst<'s>) -> Self { - Self(VecDeque::from([dsl_val(cst.val())]).into()) - } -} - -impl<'s> From> for Ast { - fn from (cst: CstIter<'s>) -> Self { - Self(cst.map(|x|x.value.into()).collect::>().into()) - } -} - -impl<'s> From> for Ast { - fn from (cst: CstVal<'s>) -> Self { - Self(VecDeque::from([dsl_val(cst.val())]).into()) - } -} - -impl<'s> From>> for DslVal, Ast> { - fn from (cst: DslVal<&'s str, CstIter<'s>>) -> Self { - dsl_val(cst) - } -} diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs deleted file mode 100644 index 984bd4e..0000000 --- a/dsl/src/cst.rs +++ /dev/null @@ -1,312 +0,0 @@ -//! The concrete syntax tree (CST) implements zero-copy -//! parsing of the DSL from a string reference. CST items -//! preserve info about their location in the source. -use crate::*; - -/// CST stores strings as source references and expressions as [CstIter] instances. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Cst<'s>(pub CstIter<'s>); -impl<'s> Dsl for Cst<'s> { - type Str = &'s str; - type Exp = CstIter<'s>; - fn nth (&self, index: usize) -> Option> { - self.0.nth(index) - } -} - -/// Parsed substring with range and value. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstVal<'s> { - /// Meaning of token. - pub value: DslVal<&'s str, CstIter<'s>>, - /// Reference to source text. - pub source: &'s str, - /// Index of 1st character of token. - pub start: usize, - /// Length of token. - pub length: usize, -} -impl<'s> Dsl for CstVal<'s> { - type Str = &'s str; - type Exp = CstIter<'s>; - fn nth (&self, index: usize) -> Option> { - todo!() - } -} - -impl<'s> CstVal<'s> { - pub const fn new ( - source: &'s str, - start: usize, - length: usize, - value: DslVal<&'s str, CstIter<'s>> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'s self) -> &'s str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'s self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'s self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: DslVal<&'s str, CstIter<'s>>) -> Self { - Self { value, ..self } - } - pub const fn value (&self) -> DslVal<&'s str, CstIter<'s>> { - self.value - } - pub const fn error (self, error: DslErr) -> Self { - Self { value: DslVal::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) { - Result::Ok(n) => Self { value: DslVal::Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: DslVal::Err(e), ..self.grow() }, - } - } - pub const fn grow_key (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Key(token.slice_source(self.source))) - } - pub const fn grow_sym (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Sym(token.slice_source(self.source))) - } - pub const fn grow_str (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Str(token.slice_source(self.source))) - } - pub const fn grow_exp (self) -> Self { - let token = self.grow(); - if let DslVal::Exp(depth, _) = token.value() { - token.with_value(DslVal::Exp(depth, CstIter::new(token.slice_source_exp(self.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let DslVal::Exp(depth, source) = token.value() { - token.with_value(DslVal::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let DslVal::Exp(depth, source) = token.value() { - if depth > 0 { - token.with_value(DslVal::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} - -/// Provides a native [Iterator] API over [CstConstIter], -/// emitting [Cst] items. -/// -/// [Cst::next] returns just the [Cst] and mutates `self`, -/// instead of returning an updated version of the struct as [CstConstIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstIter<'s>(pub CstConstIter<'s>); -impl<'s> Dsl for CstIter<'s> { - type Str = &'s str; - type Exp = Self; - fn nth (&self, index: usize) -> Option> { - self.0.nth(index).map(|x|dsl_val(x)) - } -} -impl<'s> CstIter<'s> { - pub const fn new (source: &'s str) -> Self { - Self(CstConstIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} -impl<'s> Iterator for CstIter<'s> { - type Item = CstVal<'s>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{ - self.0 = rest; - item - }) - } -} -impl<'s> Into>> for CstIter<'s> { - fn into (self) -> Vec> { - self.collect() - } -} -impl<'s> Into> for CstIter<'s> { - fn into (self) -> Vec { - self.map(Into::into).collect() - } -} - -/// Owns a reference to the source text. -/// [CstConstIter::next] emits subsequent pairs of: -/// * a [Cst] and -/// * the source text remaining -/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstConstIter<'s>(pub &'s str); -impl<'s> Dsl for CstConstIter<'s> { - type Str = &'s str; - type Exp = Self; - fn nth (&self, mut index: usize) -> Option> { - use DslVal::*; - let mut iter = self.clone(); - for i in 0..index { - iter = iter.next()?.1 - } - iter.next().map(|(x, _)|dsl_val(x.value)) - } -} -impl<'s> CstConstIter<'s> { - pub const fn new (source: &'s 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<(CstVal<'s>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(CstVal<'s>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} -impl<'s> From> for CstIter<'s> { - fn from (iter: CstConstIter<'s>) -> Self { - Self(iter) - } -} -impl<'s> From> for CstConstIter<'s> { - fn from (iter: CstIter<'s>) -> Self { - iter.0 - } -} - -/// Implement the const iterator pattern. -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; - } - } -} - -const_iter!(<'s>|self: CstConstIter<'s>| - => CstVal<'s> - => self.next_mut().map(|(result, _)|result)); - -/// Static iteration helper used by [cst]. -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 <'s> (source: &'s str) -> Option> { - use DslVal::*; - let mut token: CstVal<'s> = CstVal::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(), - '(' => - CstVal::new(source, start, 1, Exp(1, CstIter::new(str_range(source, start, start + 1)))), - '"' => - CstVal::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - CstVal::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - CstVal::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - CstVal::new(source, start, 1, match to_digit(c) { - Ok(c) => DslVal::Num(c), - Result::Err(e) => DslVal::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) -> DslResult { - 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) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs deleted file mode 100644 index 00f83b3..0000000 --- a/dsl/src/dsl.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::*; -use std::error::Error; - -/// Standard result type for DSL-specific operations. -pub type DslResult = Result; - -/// DSL-specific error codes. -#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum DslErr { - #[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), -} - -/// Enumeration of possible DSL tokens. -/// Generic over string and expression storage. -/// -/// * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] -/// * [DslVal::Exp] wraps an expression depth and a [CstIter] -/// 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 -/// * [DslVal::Sym] and [DslVal::Key] are stringish literals -/// with slightly different parsing rules. -/// * [DslVal::Num] is an unsigned integer literal. -#[derive(Clone, Debug, PartialEq, Default)] -pub enum DslVal { - #[default] - Nil, - Err(DslErr), - Num(usize), - Sym(Str), - Key(Str), - Str(Str), - Exp(usize, Exp), -} - -pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { - use DslVal::*; - match val { - Nil => Nil, - Err(e) => Err(e), - Num(u) => Num(u), - Sym(s) => Sym(s.into()), - Key(s) => Key(s.into()), - Str(s) => Str(s.into()), - Exp(d, x) => Exp(d, x.into()), - } -} - -pub trait Dsl: Debug { - type Str: PartialEq + Clone + Default + Debug + AsRef; - type Exp: PartialEq + Clone + Default + Debug + Dsl; - fn nth (&self, index: usize) -> Option>; - fn val (&self) -> DslVal { - self.nth(0).unwrap_or(DslVal::Nil) - } - // exp-only nth here? -} - -impl< - Str: PartialEq + Clone + Default + Debug + AsRef, - Exp: PartialEq + Clone + Default + Debug + Dsl, -> Dsl for DslVal { - type Str = Str; - type Exp = Exp; - fn val (&self) -> DslVal { - self.clone() - } - fn nth (&self, _index: usize) -> Option> { - todo!() - } -} - -/// May construct self from state and DSL. -pub trait DslFrom: Sized { - fn try_dsl_from (state: &State, value: &impl Dsl) -> Perhaps; - fn dsl_from ( - state: &State, value: &impl Dsl, error: impl Fn()->Box - ) -> Usually { - match Self::try_dsl_from(state, value)? { - Some(value) => Ok(value), - _ => Err(error()) - } - } -} - -/// May construct another from self and DSL. -pub trait DslInto { - fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into ( - &self, value: &impl Dsl, error: impl Fn()->Box - ) -> Usually { - match Self::try_dsl_into(self, value)? { - Some(value) => Ok(value), - _ => Err(error()) - } - } -} - -impl DslVal { - pub fn is_nil (&self) -> bool { - matches!(self, Self::Nil) - } - pub fn as_err (&self) -> Option<&DslErr> { - if let Self::Err(e) = self { Some(e) } else { None } - } - pub fn as_num (&self) -> Option { - if let Self::Num(n) = self { Some(*n) } else { None } - } - pub fn as_exp (&self) -> Option<&Exp> { - if let Self::Exp(_, x) = self { Some(x) } else { None } - } - pub fn exp_depth (&self) -> Option { - todo!() - } - pub fn exp_head (&self) -> Option<&Self> { - todo!() - } // TODO - pub fn exp_tail (&self) -> Option<&Exp> { - todo!() - } // TODO - pub fn peek (&self) -> Option { - todo!() - } - pub fn next (&mut self) -> Option { - todo!() - } - pub fn rest (self) -> Vec { - todo!() - } -} - -impl Copy for DslVal {} - -impl, Exp> DslVal { - pub fn as_sym (&self) -> Option<&str> { - if let Self::Sym(s) = self { Some(s.as_ref()) } else { None } - } - pub fn as_key (&self) -> Option<&str> { - if let Self::Key(k) = self { Some(k.as_ref()) } else { None } - } - pub fn as_str (&self) -> Option<&str> { - if let Self::Str(s) = self { Some(s.as_ref()) } else { None } - } - pub fn exp_match (&self, namespace: &str, cb: F) -> Perhaps - where F: Fn(&str, &Exp)-> Perhaps { - if let Some(Self::Key(key)) = self.exp_head() - && key.as_ref().starts_with(namespace) - && let Some(tail) = self.exp_tail() { - cb(key.as_ref().split_at(namespace.len()).1, tail) - } else { - Ok(None) - } - } -} - -macro_rules! from_str { - ($Struct:ty |$source:ident| $expr:expr) => { - impl<'s> From<&'s str> for $Struct { - fn from ($source: &'s str) -> Self { - $expr - } - } - } -} - -from_str!(Ast|source|Self::from(CstIter::from(source))); -from_str!(Cst<'s>|source|Self(CstIter(CstConstIter(source)))); -from_str!(CstIter<'s>|source|Self(CstConstIter(source))); -from_str!(CstConstIter<'s>|source|Self::new(source)); diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 264fa19..3e26227 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,17 +1,376 @@ #![feature(adt_const_params)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] +#![feature(const_precise_live_drops)] +extern crate const_panic; +use const_panic::{concat_panic, PanicFmt}; pub(crate) use ::tengri_core::*; +pub(crate) use std::error::Error; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; pub(crate) use std::collections::VecDeque; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; -pub(crate) use self::DslErr::*; - -mod dsl; pub use self::dsl::*; -mod ast; pub use self::ast::*; -mod cst; pub use self::cst::*; - +pub(crate) use self::DslError::*; #[cfg(test)] mod test; + +pub type DslUsually = Result; +pub type DslPerhaps = Result, DslError>; + +/// Pronounced dizzle. +pub trait Dsl: Clone + Debug { + /// The string representation for a dizzle. + type Str: DslStr; + /// The expression representation for a dizzle. + type Exp: DslExp; + /// Return a token iterator for this dizzle. + fn dsl (&self) -> DslUsually<&Val>; +} + +/// Enumeration of values representable by a DSL [Token]s. +/// Generic over string and expression storage. +#[derive(Clone, Debug, PartialEq, Default)] +pub enum Val { + #[default] + Nil, + /// Unsigned integer literal + Num(usize), + /// Tokens that start with `:` + Sym(D::Str), + /// Tokens that don't start with `:` + Key(D::Str), + /// Quoted string literals + Str(D::Str), + /// Expressions. + Exp( + /// Expression depth checksum. Must be 0, otherwise you have an unclosed delimiter. + usize, + /// Expression content. + D::Exp + ), + Error(DslError), +} + +impl> Copy for Val {} + +impl Val { + pub fn convert (&self) -> Val where + B::Str: for<'a> From<&'a D::Str>, + B::Exp: for<'a> From<&'a D::Exp> + { + match self { Val::Nil => Val::Nil, + Val::Num(u) => Val::Num(*u), + Val::Sym(s) => Val::Sym(s.into()), + Val::Key(s) => Val::Key(s.into()), + Val::Str(s) => Val::Str(s.into()), + Val::Exp(d, x) => Val::Exp(*d, x.into()), + Val::Error(e) => Val::Error(*e) } } + pub fn is_nil (&self) -> bool { matches!(self, Self::Nil) } + pub fn as_error (&self) -> Option<&DslError> { if let Self::Error(e) = self { Some(e) } else { None } } + pub fn as_num (&self) -> Option {match self{Self::Num(n)=>Some(*n),_=>None}} + pub fn as_sym (&self) -> Option<&str> {match self{Self::Sym(s )=>Some(s.as_ref()),_=>None}} + pub fn as_key (&self) -> Option<&str> {match self{Self::Key(k )=>Some(k.as_ref()),_=>None}} + pub fn as_str (&self) -> Option<&str> {match self{Self::Str(s )=>Some(s.as_ref()),_=>None}} + pub fn as_exp (&self) -> Option<&D::Exp> {match self{Self::Exp(_, x)=>Some(x),_=>None}} + pub fn exp_depth (&self) -> Option { todo!() } + pub fn exp_head_tail (&self) -> (Option<&Self>, Option<&D::Exp>) { (self.exp_head(), self.exp_tail()) } + pub fn exp_head (&self) -> Option<&Self> { todo!() } // TODO + pub fn exp_tail (&self) -> Option<&D::Exp> { todo!() } + pub fn peek (&self) -> Option { todo!() } + pub fn next (&mut self) -> Option { todo!() } + pub fn rest (self) -> Vec { todo!() } + //pub fn exp_match (&self, namespace: &str, cb: F) -> DslPerhaps + //where F: Fn(&str, &Exp)-> DslPerhaps { + //if let Some(Self::Key(key)) = self.exp_head() + //&& key.as_ref().starts_with(namespace) + //&& let Some(tail) = self.exp_tail() { + //cb(key.as_ref().split_at(namespace.len()).1, tail) + //} else { + //Ok(None) + //} + //} +} + +/// The string representation for a [Dsl] implementation. +/// [Cst] uses `&'s str`. [Ast] uses `Arc`. +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} +impl + std::ops::Deref> DslStr for T {} + +/// The expression representation for a [Dsl] implementation. +/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. +pub trait DslExp: PartialEq + Clone + Default + Debug {} +impl DslExp for T {} + +/// The abstract syntax tree (AST) can be produced from the CST +/// by cloning source slices into owned ([Arc]) string slices. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Ast(Token); +impl Dsl for Ast { + type Str = Arc; + type Exp = VecDeque>>; + fn dsl (&self) -> DslUsually<&Val> { + Ok(self.0.value()) + } +} + +/// The concrete syntax tree (CST) implements zero-copy +/// parsing of the DSL from a string reference. CST items +/// preserve info about their location in the source. +/// CST stores strings as source references and expressions as [CstIter] instances. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Cst<'s>(Token>); +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = CstConstIter<'s>; + fn dsl (&self) -> DslUsually<&Val> { + Ok(self.0.value()) + } +} + +/// `State` + [Dsl] -> `Self`. +pub trait FromDsl: Sized { + fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; + fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_from_dsl(state, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } + +/// `self` + `Options` -> [Dsl] +pub trait IntoDsl { /*TODO*/ } + +/// `self` + [Dsl] -> `Item` +pub trait DslInto { + fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } + +/// `self` + `Item` -> [Dsl] +pub trait DslFrom { /*TODO*/ } +/// Standard result type for DSL-specific operations. +pub type DslResult = Result; +/// DSL-specific error codes. +#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { + #[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), + #[error("end reached")] + End +} +/// Provides native [Iterator] API over [CstConstIter], emitting [Cst] items. +/// +/// [Cst::next] returns just the [Cst] and mutates `self`, +/// instead of returning an updated version of the struct as [CstConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstIter<'s>(CstConstIter<'s>); +impl<'s> CstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(CstConstIter::new(source)) } +} +impl<'s> Iterator for CstIter<'s> { + type Item = Token>; + fn next (&mut self) -> Option { + match self.0.advance() { + Ok(Some((item, rest))) => { self.0 = rest; item.into() }, + Ok(None) => None, + Err(e) => panic!("{e:?}") + } + } +} +/// Holds a reference to the source text. +/// [CstConstIter::next] emits subsequent pairs of: +/// * a [Cst] and +/// * the source text remaining +/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstConstIter<'s>(pub &'s str); +impl<'s> From <&'s str> for CstConstIter<'s> { + fn from (src: &'s str) -> Self { Self(src) } +} +impl<'s> Iterator for CstConstIter<'s> { + type Item = Token>; + fn next (&mut self) -> Option>> { self.advance().unwrap().map(|x|x.0) } +} +impl<'s> ConstIntoIter for CstConstIter<'s> { + type Kind = IsIteratorKind; + type Item = Cst<'s>; + type IntoIter = Self; +} +impl<'s> CstConstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(source) } + pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } + pub const fn peek (&self) -> DslPerhaps>> { Token::peek(self.0) } + //pub const fn next (mut self) -> Option<(Token>, Self)> { + //Self::advance(&mut self).unwrap() } + pub const fn advance (&mut self) -> DslPerhaps<(Token>, Self)> { + match self.peek() { + Ok(Some(token)) => { + let end = self.chomp(token.span.end()); + Ok(Some((token.copy(), end))) + }, + Ok(None) => Ok(None), + Err(e) => Err(e) + } + } +} + +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct Span { + /// Reference to source text. + pub source: D::Str, + /// Index of 1st character of span. + pub start: usize, + /// Length of span. + pub length: usize, +} +impl<'s, D: Dsl> Span { + pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } +} +impl<'s, D: Dsl> Span { + pub const fn slice (&self) -> &'s str { + str_range(self.source, self.start, self.end()) } + pub const fn slice_exp (&self) -> &'s str { + str_range(self.source, self.start.saturating_add(1), self.end()) } + pub const fn grow (&mut self) -> DslUsually<&mut Self> { + if self.length + self.start >= self.source.len() { return Err(End) } + self.length = self.length.saturating_add(1); + Ok(self) } +} + +/// Parsed substring with range and value. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Token { + /// Source span of token. + span: Span, + /// Meaning of token. + value: Val, +} +impl Token { + pub const fn value (&self) -> &Val { &self.value } + pub const fn span (&self) -> &Span { &self.span } + pub const fn err (&self) -> Option { + if let Val::Error(e) = self.value { Some(e) } else { None } } + pub const fn new (source: D::Str, start: usize, length: usize, value: Val) -> Self { + Self { value, span: Span { source, start, length } } } + pub const fn copy (&self) -> Self where D::Str: Copy, D::Exp: Copy { + Self { span: Span { ..self.span }, value: self.value } } +} +const fn or_panic (result: DslUsually) -> T { + match result { Ok(t) => t, Err(e) => const_panic::concat_panic!(e) } +} +impl<'s, D: Dsl> Token { + pub const fn peek (src: D::Str) -> DslPerhaps where D::Exp: From<&'s str> { + use Val::*; + let mut t = Self::new(src, 0, 0, Nil); + let mut iter = char_indices(src); + while let Some(((i, c), next)) = iter.next() { + t = match (t.value(), c) { + (Error(_), _) => + return Ok(Some(t)), + (Nil, ' '|'\n'|'\r'|'\t') => + *or_panic(t.grow()), + (Nil, '(') => + Self::new(src, i, 1, Exp(1, D::Exp::from(str_range(src, i, i + 1)))), + (Nil, '"') => + Self::new(src, i, 1, Str(str_range(src, i, i + 1))), + (Nil, ':'|'@') => + Self::new(src, i, 1, Sym(str_range(src, i, i + 1))), + (Nil, '/'|'a'..='z') => + Self::new(src, i, 1, Key(str_range(src, i, i + 1))), + (Nil, '0'..='9') => + Self::new(src, i, 1, match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }), + (Nil, _) => + { t.value = Val::Error(Unexpected(c)); t }, + (Str(_), '"') => + return Ok(Some(t)), + (Str(_), _) => + { or_panic(t.grow()); t.value = Str(t.span.slice()); t }, + (Num(m), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Num(m), _) => match to_digit(c) { + Ok(n) => { t.grow()?; t.value = Num(10*m+n); t }, + Err(e) => { t.grow()?; t.value = Error(e); t } }, + (Sym(_), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Sym(_), 'a'..='z'|'A'..='Z'|'0'..='9'|'-') => { + t.grow()?; t.value = Sym(t.span.slice()); t }, + (Sym(_), _) => + { t.value = Error(Unexpected(c)); t }, + (Key(_), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Key(_), 'a'..='z'|'0'..='9'|'-'|'/') => + { t.grow()?; t.value = Key(t.span.slice()); t }, + (Key(_), _ ) => + { t.value = Error(Unexpected(c)); t }, + (Exp(0, _), _) => + { t.grow()?; t.value = Exp(0, D::Exp::from(t.span.slice_exp())); return Ok(Some(t)) }, + (Exp(d, _), ')') => + { t.grow()?; t.value = Exp(d-1, D::Exp::from(t.span.slice_exp())); t }, + (Exp(d, _), '(') => + { t.grow()?; t.value = Exp(d+1, D::Exp::from(t.span.slice_exp())); t }, + (Exp(d, _), _ ) => + { t.grow()?; t.value = Exp(*d, D::Exp::from(t.span.slice_exp())); t }, + }; + iter = next; + } + Ok(match t.value() { + Nil => None, + _ => Some(t) + }) + } + pub const fn grow (&mut self) -> DslUsually<&mut Self> { self.span.grow()?; Ok(self) } + pub const fn grow_exp (&mut self, d: isize) -> &mut Self where D::Exp: From<&'s str> { + if let Val::Exp(depth, _) = self.value() { + self.value = Val::Exp((*depth as isize + d) as usize, D::Exp::from(self.span.slice_exp())); + self + } else { + unreachable!() + } + } +} + +pub const fn to_digit (c: char) -> DslResult { + 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)) }) } + +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 to_number (digits: &str) -> DslResult { + 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) } + +/// Implement type conversions. +macro_rules! from(($($Struct:ty { $( + $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr +);+ $(;)? })*) => { $( + $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { + fn from ($source: $From) -> Self { $expr } + })+ +)* }); + +//from! { + ////Vec> { <'s> (val: CstIter<'s>) val.collect(); } + //CstConstIter<'s> { + //<'s> (src: &'s str) Self(src); + //<'s> (iter: CstIter<'s>) iter.0; } + //CstIter<'s> { + //<'s> (src: &'s str) Self(CstConstIter(src)); + //<'s> (iter: CstConstIter<'s>) Self(iter); } + //Cst<'s> { <'s> (src: &'s str) Self(CstIter(CstConstIter(src))); } + //Vec { <'s> (val: CstIter<'s>) val.map(Into::into).collect(); } + //Token> { <'s> (token: Token>) Self { value: token.value.into(), span: token.span.into() } } + //Ast { + //<'s> (src: &'s str) Ast::from(CstIter(CstConstIter(src))); + //<'s> (cst: Cst<'s>) Ast(VecDeque::from([dsl_val(cst.val())]).into()); + //<'s> (iter: CstIter<'s>) Ast(iter.map(|x|x.value.into()).collect::>().into()); + // (token: Token) Ast(VecDeque::from([dsl_val(token.val())]).into()); } +//} diff --git a/dsl/src/test.rs b/dsl/src/test.rs index b275573..dfb37f4 100644 --- a/dsl/src/test.rs +++ b/dsl/src/test.rs @@ -12,10 +12,10 @@ use proptest::prelude::*; } #[test] fn test_num () { - let _digit = to_digit('0'); - let _digit = to_digit('x'); - let _number = to_number(&"123"); - let _number = to_number(&"12asdf3"); + let _digit = Token::to_digit('0'); + let _digit = Token::to_digit('x'); + let _number = Token::to_number(&"123"); + let _number = Token::to_number(&"12asdf3"); } //proptest! { //#[test] fn proptest_source_iter ( @@ -48,15 +48,15 @@ use proptest::prelude::*; //} #[test] fn test_token () -> Result<(), Box> { - use crate::DslVal::*; + use crate::Val::*; let source = ":f00"; - let mut token = CstVal::new(source, 0, 1, Sym(":")); + let mut token = CstToken::new(source, 0, 1, Sym(":")); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 2, Sym(":f"))); + assert_eq!(token, CstToken::new(source, 0, 2, Sym(":f"))); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 3, Sym(":f0"))); + assert_eq!(token, CstToken::new(source, 0, 3, Sym(":f0"))); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 4, Sym(":f00"))); + assert_eq!(token, CstToken::new(source, 0, 4, Sym(":f00"))); assert_eq!(None, CstIter::new("").next()); assert_eq!(None, CstIter::new(" \n \r \t ").next()); diff --git a/output/src/ops.rs b/output/src/ops.rs index 9c528f2..13ade31 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -380,7 +380,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] }); -/// Enabling the `dsl` feature implements [DslFrom] for +/// Enabling the `dsl` feature implements [FromDsl] for /// the layout elements that are provided by this crate. #[cfg(feature = "dsl")] mod ops_dsl { use crate::*; @@ -391,7 +391,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ //($Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$($arg:ident $(:$ty:ty)?),*] $expr:expr) //)*) => { //$( - //impl DslFrom for $Struct$(<$($A),+>)? { + //impl FromDsl for $Struct$(<$($A),+>)? { //fn try_dsl_from ( //state: &S, dsl: &impl Dsl //) -> Perhaps { @@ -407,7 +407,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { - impl DslFrom for $Struct$(<$($A),+>)? { + impl FromDsl for $Struct$(<$($A),+>)? { fn try_dsl_from ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { @@ -422,7 +422,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { - impl DslFrom for $Struct$(<$($A),+>)? { + impl FromDsl for $Struct$(<$($A),+>)? { fn try_dsl_from ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 06b184e..132d867 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -84,7 +84,7 @@ impl ToTokens for CommandDef { let mut out = TokenStream2::new(); for (arg, _ty) in arm.args() { write_quote_to(&mut out, quote! { - #arg: DslFrom::dsl_from(self, words, ||"command error")?, + #arg: FromDsl::from_dsl(self, words, ||"command error")?, }); } out @@ -149,8 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl ::tengri::dsl::DslFrom<#state> for #command_enum { - fn try_dsl_from ( + impl ::tengri::dsl::FromDsl<#state> for #command_enum { + fn try_from_dsl ( state: &#state, value: &impl ::tengri::dsl::Dsl ) -> Perhaps { diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index d0f643b..ca5c9e7 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -57,7 +57,6 @@ impl ToTokens for ExposeDef { impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { let Self(block, exposed) = self; - let state = &block.self_ty; write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { self.expose_variants(out, t, variants); @@ -80,8 +79,8 @@ impl ExposeImpl { let arms = Self::with_predefined(t, quote! { #(#arms)* }); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::DslFrom<#state> for #t { - fn try_dsl_from (state: &#state, dsl: &impl Dsl) -> Perhaps { + impl ::tengri::dsl::FromDsl<#state> for #t { + fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { Ok(Some(match dsl.val() { #arms _ => { return Ok(None) } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 12012c3..0f377de 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -39,26 +39,22 @@ impl Parse for ViewImpl { impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; - let self_ty = &block.self_ty; - // Expressions are handled by built-in functions - // that operate over constants and symbols. - let builtin = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { - ::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( - #builtin::dsl_from(self, expr, ||Box::new("failed to load builtin".into()))? - .boxed() - )), - }); - // Symbols are handled by user-taked functions - // that take no parameters but `&self`. - let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { - ::tengri::dsl::DslVal::Sym(#key) => return Ok(Some( - self.#value().boxed() - )), - })); + let Self(_, ViewImpl { block, .. }) = self; + let generated = self.generated(); write_quote_to(out, quote! { - // Original user-taked implementation: #block + #generated + }) + } +} + +impl ViewDef { + fn generated (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let self_ty = &block.self_ty; + let builtins = self.builtins(); + let exposed = self.exposed(); + quote! { /// Generated by [tengri_proc]. /// /// Makes [#self_ty] able to construct the [Render]able @@ -70,11 +66,30 @@ impl ToTokens for ViewDef { fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { - use ::tengri::dsl::DslVal::*; - Ok(match dsl.val() { #(#builtin)* #(#exposed)* _ => return Ok(None) }) + Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) } } - }) + } + } + /// Expressions are handled by built-in functions + /// that operate over constants and symbols. + fn builtins (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { + ::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( + #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? + .boxed() + )), + }); + quote! { #(#builtins)* } + } + /// Symbols are handled by user-taked functions that take no parameters but `&self`. + fn exposed (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { + #key => return Ok(Some(self.#value().boxed())), + })); + quote! { ::tengri::dsl::DslVal::Sym(key) => match key.as_ref() { #(#exposed)* } } } }