modularize eval
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
facile pop culture reference 2026-08-02 15:18:56 +03:00
parent 64c83b5460
commit eb028c85fc
12 changed files with 322 additions and 216 deletions

View file

@ -41,6 +41,33 @@ macro_rules! features {
}
}
macro_rules! fn_kw_layout {
($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => {
pub fn $name <O: Screen, S> (
$state: &S, $output: &mut O, $expr: &str
) -> Perhaps<XYWH<O::Unit>> where
S: Interpret<O, Option<XYWH<O::Unit>>>
+ for<'b> Namespace<'b, bool>
+ for<'b> Namespace<'b, O::Unit>
+ for<'b> Namespace<'b, Option<O::Unit>>
$body
}
}
macro_rules! fn_kw_layout_tui {
($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => {
pub fn $name <S> (
$state: &S, $output: &mut Tui, $expr: &str
) -> Perhaps<XYWH<u16>> where
S: Interpret<Tui, Option<XYWH<u16>>>
+ for<'b> Namespace<'b, bool>
+ for<'b> Namespace<'b, u16>
+ for<'b> Namespace<'b, Option<u16>>
+ for<'b> Namespace<'b, Color>
$body
}
}
#[cfg(feature = "lang")] pub use ::dizzle::{Usually, Perhaps};
#[cfg(feature = "lang")] use ::dizzle::*;
@ -168,7 +195,7 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
/// Some layout operations exist in multiple variants that take a single argument.
/// Their handling in [eval_view] is uniform and goes like this:
macro_rules! eval_enum ((
$name:literal, $output:ident, $state:ident, $value:expr, $arg0: ident, $Enum:ident {
$name:literal, $output:ident, $state:ident, $value:expr, $arg0:expr, $Enum:ident {
$($v:literal => $V:ident),* $(,)?
}
) => {{
@ -187,7 +214,7 @@ macro_rules! eval_xy (
// (fill/xy ...)
(
$name:expr => $expr:expr, $head:expr, $output:ident, $state:ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg: ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg:expr,
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
@ -210,7 +237,7 @@ macro_rules! eval_xy (
// (push/xy n m ...)
(
$name:expr => $expr:expr, $head:expr, $output:ident, $state:ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg0: ident, $arg1: ident, $arg2: ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg0:expr, $arg1:expr, $arg2:expr,
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
@ -483,7 +510,7 @@ macro_rules! eval_xy (
///
/// ```
/// # use tengri::*;
/// let _ = draw(|to: &mut Tui|Ok(Some(to.1)));
/// let _ = draw(|to: &mut Tui|Ok(Some(to.area()))); // draws nothing
/// ```
pub const fn draw <T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> (
item: F
@ -1550,96 +1577,6 @@ macro_rules! eval_xy (
})
}
/// Interpret TUI-specific layout operation.
///
/// ```
/// use tengri::{*, dizzle::*, ratatui::prelude::Color};
///
/// #[namespace(bool)]
/// #[namespace(u8)]
/// #[namespace(u16)]
/// #[namespace(Color get_color)]
/// struct State;
///
/// impl Interpret<Tui, Option<XYWH<u16>>> for State {
/// fn interpret_expr <'a> (&'a self, _: &mut Tui, lang: &'a impl Expression)
/// -> Usually<Option<XYWH<u16>>>
/// {
/// Ok(None)
/// }
/// }
///
/// fn get_color (state: &State, src: impl Language) -> Perhaps<Color> {
/// Ok(if let Some(color) = Tui::eval_color_expr(state, &src)? {
/// Some(color)
/// } else if let Some(sym) = src.word()? {
/// Some(match sym {
/// ":color/bg" => Color::Rgb(28, 32, 36),
/// ":color/fg" => Color::Rgb(98, 92, 96),
/// _ => return Err(format!("not a color: {sym}").into())
/// })
/// } else {
/// return Err(format!("not a color: {:?}", src.src()?).into())
/// })
/// }
///
/// # fn main () -> tengri::Usually<()> {
/// let state = State;
/// let mut out = Tui::new(80, 25);
/// Tui::eval_view(&state, &mut out, "")?;
/// Tui::eval_view(&state, &mut out, "text Hello world!")?;
/// Tui::eval_view(&state, &mut out, "fg (g 0) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "bg (g 2) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
/// # Ok(()) }
/// ```
pub fn eval_view <'a, S> (
state: &S, to: &mut Tui, expr: impl Expression + 'a
) -> Perhaps<XYWH<u16>> where
S: Interpret<Tui, Option<XYWH<u16>>>
+ for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, u16>
+ for<'b>Namespace<'b, Color>
{
// See `tengri::eval_view`
let head = expr.head()?;
let mut frags = head.src()?.unwrap_or_default().split("/");
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
match frags.next() {
Some("text") => {
if let Some(src) = args?.src()? {
src.draw(to)
} else {
return Ok(None)
}
},
Some("fg") => {
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
if let Some(color) = Namespace::namespace(state, arg0)? {
fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
} else {
return Err(format!("fg: {arg0:?}: not a color").into())
}
},
Some("bg") => {
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
if let Some(color) = Namespace::namespace(state, arg0)? {
bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
} else {
return Err(format!("bg: {arg0:?}: not a color").into())
}
},
_ => return Ok(None)
}
}
pub fn eval_color_expr (state: &impl for<'a> Namespace<'a, u8>, src: impl Language)
-> Perhaps<Color>
{
@ -1971,115 +1908,271 @@ macro_rules! eval_xy (
#[cfg(feature = "eval")] mod eval {
use crate::*;
/// Interpret layout operation.
///
/// ```
/// # use tengri::{*, dizzle::*};
/// # use ::tengri::{*, dizzle::*, ratatui::prelude::Color};
///
/// struct State {/*app-specific*/}
/// impl<'b> Namespace<'b, u16> for State {}
/// impl<'b> Namespace<'b, bool> for State {}
/// impl<'b> Namespace<'b, Option<u16>> for State {}
/// impl Interpret<Tui, Option<XYWH<u16>>> for State {}
/// #[namespace(bool)]
/// #[namespace(u8)]
/// #[namespace(u16)]
/// #[namespace(Option<u16>)]
/// #[namespace(Color get_color)]
/// struct App;
///
/// fn get_color (state: &App, src: impl Language) -> Perhaps<Color> {
/// Ok(if let Some(color) = Tui::eval_color_expr(state, &src)? {
/// Some(color)
/// } else if let Some(sym) = src.word()? {
/// Some(match sym {
/// ":color/bg" => Color::Rgb(28, 32, 36),
/// ":color/fg" => Color::Rgb(98, 92, 96),
/// _ => return Err(format!("not a color: {sym}").into())
/// })
/// } else {
/// return Err(format!("not a color: {:?}", src.src()?).into())
/// })
/// }
///
/// impl Interpret<Tui, Option<XYWH<u16>>> for App {
/// fn interpret_expr <'a> (&'a self, _: &mut Tui, lang: &'a impl Expression)
/// -> Usually<Option<XYWH<u16>>>
/// {
/// Ok(None)
/// }
/// }
///
/// impl Keywords<Tui, XYWH<u16>> for App {
/// fn keywords () -> impl Iterator<Item = fn(&Self, &mut Tui, &str) -> Perhaps<XYWH<u16>>> {
/// [
/// kw_when, kw_either, kw_split, kw_align,
/// kw_exact, kw_fixed, kw_min, kw_max, kw_push,
/// kw_tui_text, kw_tui_fg, kw_tui_bg
/// ].into_iter()
/// }
/// }
///
/// # fn main () -> tengri::Usually<()> {
/// let state = State {};
/// let mut target = Tui::new(80, 25);
/// eval_view(&state, &mut target, &"")?;
/// eval_view(&state, &mut target, &"(whe true (text hello))")?;
/// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?;
/// // TODO test all
/// let state = App;
/// let mut out = Tui::new(80, 25);
/// Tui::eval_view(&state, &mut out, "")?;
/// Tui::eval_view(&state, &mut out, "text Hello world!")?;
/// Tui::eval_view(&state, &mut out, "fg (g 0) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "bg (g 2) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
/// # Ok(()) }
/// ```
pub fn eval_view <'a, O: Screen + 'a, S> (
state: &S, output: &mut O, expr: &'a impl Expression
) -> Perhaps<XYWH<O::Unit>> where
S: Interpret<O, Option<XYWH<O::Unit>>>
+ for<'b> Namespace<'b, bool>
+ for<'b> Namespace<'b, O::Unit>
+ for<'b> Namespace<'b, Option<O::Unit>>
{
// First element of expression is name of the operation.
// These are quasi-namespaced using the separator character, `/`.
let head = expr.head()?;
let mut frags = head.src()?.unwrap_or_default().split("/");
// The rest of the tokens in the expr are arguments.
// Their meanings depend on the dispatched operation
// Here we just reference them, so that they are in scope.
// Dereferencing them happens in the dispatch branch.
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
// First `frags.next()` calls returns the namespace.
match frags.next() {
Some("when") => when(
state.namespace(arg0?)?.unwrap(),
draw(move|output: &mut O|{state.interpret(output, &arg1)})
).draw(output),
Some("either") => either(
state.namespace(arg0?)?.unwrap(),
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
draw(move|output: &mut O|{state.interpret(output, &arg2)}),
).draw(output),
Some("bsp") => eval_enum!("bsp", output, state, frags.next(), arg0, Split {
"n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below
}).stack(
draw(move|output: &mut O|{state.interpret(output, &arg0)}),
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
).draw(output),
Some("align") => draw(move|output: &mut O|{state.interpret(output, &arg0)})
.align(eval_enum!("align", output, state, frags.next(), arg0, Azimuth {
"c" => C, "x" => X, "y" => Y,
"n" => N, "s" => S, "e" => E, "w" => W,
"nw" => NW, "sw" => SW, "ne" => NE, "se" => SE,
})).draw(output),
Some("exact") => eval_xy!(
"exact" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h, arg0, arg1, arg2,
),
Some("fixed") => eval_xy!(
"fixed" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h, arg0, arg1, arg2,
),
Some("min") => eval_xy!(
"min" => expr, head, output, state, frags.next(), min_wh, min_w, min_h, arg0, arg1, arg2,
),
Some("max") => eval_xy!(
"max" => expr, head, output, state, frags.next(), max_wh, max_w, max_h, arg0, arg1, arg2,
),
Some("push") => eval_xy!(
"push" => expr, head, output, state, frags.next(), push_xy, push_x, push_y, arg0, arg1, arg2,
),
Some("fill") => eval_xy!(
"fill" => expr, head, output, state, frags.next(), full_wh, full_w, full_h, arg0,
),
_ => return Ok(None)
pub trait Keywords <U, V>: 'static {
fn keywords () -> impl Iterator<Item = fn(&Self, &mut U, &str) -> Perhaps<V>>;
fn keyword (&self, to: &mut U, expr: &impl Expression) -> Perhaps<V> {
if let Some(expr) = expr.src()? {
for keyword in Self::keywords() {
if let Some(result) = keyword(self, to, &expr)? {
return Ok(Some(result))
}
}
}
Ok(None)
}
}
fn invalid_variant <T> (
name: &str,
frag: impl Language,
expr: impl Language,
head: impl Language,
) -> Usually<T> {
unimplemented!(
"{name}/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next()
)
#[macro_export] macro_rules! impl_keywords {
($T:ty, $U:ty, $V:ty [ $($kw:ident),* ]) => {
impl Keywords<$T, $U> for $V {
fn keywords () -> impl Iterator<Item = fn(&Self, &mut $T, &str) -> Perhaps<$U>> {
[ $($kw),* ].into_iter()
}
}
}
}
fn_kw_layout!(kw_when |state, output, expr| {
Ok(matches!(expr.head()?, Some("when")).then(||{
when(state.namespace(expr.tail().head())?.unwrap(),
draw(move|output: &mut O|{state.interpret(output, &expr.tail().tail().head())})
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_either |state, output, expr| {
Ok(matches!(expr.head()?, Some("either")).then(||{
either(state.namespace(expr.tail().head()?)?.unwrap(),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().head()?)
}),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().tail().head()?)
}),
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_split |state, output, expr| {
let head = expr.head();
let mut frags = head.src()?.unwrap_or_default().split("/");
Ok(matches!(frags.next(), Some("bsp")).then(||{
eval_enum!("bsp", output, state, frags.next(), expr.tail().head()?, Split {
"n" => North,
"s" => South,
"e" => East,
"w" => West,
"a" => Above,
"b" => Below
}).stack(
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().head()?)
}),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().tail().head()?)
}),
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_align |state, output, expr| {
Ok(matches!(expr.head()?, Some("align")).then(||{
draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align(
eval_enum!("align", output, state,
expr.head().src()?.unwrap_or_default().split("/").skip(1).next(),
expr.tail().head(),
Azimuth {
"c" => C, "x" => X, "y" => Y,
"n" => N, "s" => S, "e" => E, "w" => W,
"nw" => NW, "sw" => SW, "ne" => NE, "se" => SE,
})
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_exact |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("exact" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_fixed |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("fixed" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_min |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("min" => expr, head, output, state, frags.next(), min_wh, min_w, min_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_max |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("max" => expr, head, output, state, frags.next(), max_wh, max_w, max_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_push |state, output, expr| {
Ok(matches!(expr.head()?, Some("push")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("push" => expr, head, output, state, frags.next(), push_xy, push_x, push_y,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
#[cfg(feature = "term")] fn_kw_layout_tui!(kw_tui_text |state, output, expr| {
Ok(matches!(expr.head()?, Some("text")).then(||{
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
if let Some(src) = args?.src()? {
src.draw(output)
} else {
return Ok(None)
}
}).transpose()?.flatten())
});
#[cfg(feature = "term")] fn_kw_layout_tui!(kw_tui_fg |state, output, expr| {
Ok(matches!(expr.head()?, Some("fg")).then(||{
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
if let Some(color) = Namespace::namespace(
state, arg0?.expect("fg: expected arg 0 (color)")
)? {
fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(output)
} else {
return Err(format!("fg: {arg0:?}: not a color").into())
}
}).transpose()?.flatten())
});
#[cfg(feature = "term")] fn_kw_layout_tui!(kw_tui_bg |state, output, expr| {
Ok(matches!(expr.head()?, Some("bg")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let mut frags = head.src()?.unwrap_or_default().split("/");
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
if let Some(color) = Namespace::namespace(state, arg0)? {
bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(output)
} else {
return Err(format!("bg: {arg0:?}: not a color").into())
}
}).transpose()?.flatten())
});
}
pub fn invalid_variant <T> (
name: &str,
frag: impl Language,
expr: impl Language,
head: impl Language,
) -> Usually<T> {
unimplemented!(
"{name}/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next()
)
}