fixes and refactors

- use macros for the evals
- move some space stuff into submodules
- partial update to doctests
This commit is contained in:
i do not exist 2026-04-24 01:34:43 +03:00
parent b44dc02f33
commit e074712459
9 changed files with 464 additions and 380 deletions

View file

@ -1,10 +1,65 @@
use crate::{*, term::*, draw::*, ratatui::prelude::*};
use dizzle::*;
use crate::{*, term::*, draw::*, space::*, lang::*, ratatui::prelude::*};
/// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments.
/// Their handling in [eval_view] is uniform and goes like this:
macro_rules! eval_xy ((
$name:expr, $expr:expr, $head:expr, $output:ident, $state:ident,
$variant:expr, $arg0: ident, $arg1: ident, $arg2: ident,
$xy:ident, $x:ident, $y:ident
) => {{
let variant = $variant;
let cb = thunk(move|output: &mut O|$state.interpret(output, &match variant {
Some("x") | Some("y") => $arg1,
Some("xy") | None => $arg2,
_ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name)
}));
match variant {
// XY variant (can be omitted)
Some("xy") | None => xy_push(
$state.namespace($arg0?)?.unwrap(),
$state.namespace($arg1?)?.unwrap(),
cb
).draw($output),
// X variant
Some("x") => x_push(
$state.namespace($arg0?)?.unwrap(),
cb
).draw($output),
// Y variant
Some("y") => y_push(
$state.namespace($arg0?)?.unwrap(),
cb
).draw($output),
// Other namespace members are invalid
frag => {
let name = $name;
let expr = $expr;
let head = $head;
unimplemented!(
"{name}/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next()
)
}
}
}});
/// 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 {
$($v:literal => $V:ident),* $(,)?
}
) => {{
match $value {
$(Some($v) => $Enum::$V,)*
frag => unimplemented!("{}/{frag:?}", $name)
}
}});
/// Interpret layout operation.
///
/// ```
/// # use tengri::*;
/// # use tengri::{*, space::*};
/// struct Target { xywh: XYWH<u16> /*platform-specific*/}
/// impl tengri::Out for Target {
/// type Unit = u16;
@ -27,137 +82,87 @@ use dizzle::*;
/// // TODO test all
/// # Ok(()) }
/// ```
#[cfg(feature = "dsl")] pub fn eval_view <'a, O: Screen + 'a, S> (
pub fn eval_view <'a, O: Screen + 'a, S> (
state: &S, output: &mut O, expr: &'a impl Expression
) -> Usually<bool> where
S: Interpret<O, ()>
+ for<'b>Namespace<'b, bool>
+ for<'b>Namespace<'b, O::Unit>
S: Interpret<O, 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 used for dispatch.
// Dispatch is proto-namespaced using separator character
// 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();
// And we also have to do the above binding dance
// so that the Perhaps<token>s remain in scope.
// First `frags.next()` calls returns the namespace.
match frags.next() {
Some("when") => output.place(&when(
Some("when") => when(
state.namespace(arg0?)?.unwrap(),
move|output: &mut O|state.interpret(output, &arg1)
)),
thunk(move|output: &mut O|state.interpret(output, &arg1))
).draw(output),
Some("either") => output.place(&either(
Some("either") => either(
state.namespace(arg0?)?.unwrap(),
move|output: &mut O|state.interpret(output, &arg1),
move|output: &mut O|state.interpret(output, &arg2),
)),
thunk(move|output: &mut O|state.interpret(output, &arg1)),
thunk(move|output: &mut O|state.interpret(output, &arg2)),
).draw(output),
Some("bsp") => output.place(&{
let a = move|output: &mut O|state.interpret(output, &arg0);
let b = move|output: &mut O|state.interpret(output, &arg1);
bsp(match frags.next() {
Some("n") => Alignment::N,
Some("s") => Alignment::S,
Some("e") => Alignment::E,
Some("w") => Alignment::W,
Some("a") => Alignment::A,
Some("b") => Alignment::B,
frag => unimplemented!("bsp/{frag:?}")
}, a, b)
}),
Some("align") => output.place(&{
let a = move|output: &mut O|state.interpret(output, &arg0).unwrap();
align(match frags.next() {
Some("c") => Alignment::Center,
Some("n") => Alignment::N,
Some("s") => Alignment::S,
Some("e") => Alignment::E,
Some("w") => Alignment::W,
Some("x") => Alignment::X,
Some("y") => Alignment::Y,
frag => unimplemented!("align/{frag:?}")
}, a)
}),
Some("fill") => output.place(&{
let a = move|output: &mut O|state.interpret(output, &arg0).unwrap();
match frags.next() {
Some("xy") | None => fill_wh(a),
Some("x") => fill_w(a),
Some("y") => fill_h(a),
frag => unimplemented!("fill/{frag:?}")
// Second `frags.next()` calls returns the namespace member.
Some("bsp") => eval_enum!(
"bsp", output, state, frags.next(), arg0, Split {
"n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below
}
}),
).half(
thunk(move|output: &mut O|state.interpret(output, &arg0)),
thunk(move|output: &mut O|state.interpret(output, &arg1)),
).draw(output),
Some("exact") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("exact: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.interpret(output, &arg).unwrap();
match axis {
Some("xy") | None => exact_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => exact_w(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => exact_h(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("exact/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next())
Some("align") => eval_enum!(
"align", output, state, frags.next(), arg0, Origin {
"c" => C, "n" => N, "s" => S, "e" => E, "w" => W, "x" => X, "y" => Y
}
}),
).align(
thunk(move|output: &mut O|state.interpret(output, &arg0))
).draw(output),
Some("min") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("min: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.interpret(output, &arg).unwrap();
match axis {
Some("xy") | None => min_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => min_w(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => min_h(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("min/{frag:?}")
}
}),
Some("max") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("max: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.interpret(output, &arg).unwrap();
match axis {
Some("xy") | None => max_wh(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => max_w(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => max_h(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("max/{frag:?}")
}
}),
Some("push") => output.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") | None => arg2, _ => panic!("push: unsupported axis {axis:?}") };
let cb = move|output: &mut O|state.interpret(output, &arg);
match axis {
Some("xy") | None => push_xy(state.namespace(arg0?)?.unwrap(), state.namespace(arg1?)?.unwrap(), cb),
Some("x") => push_x(state.namespace(arg0?)?.unwrap(), cb),
Some("y") => push_y(state.namespace(arg0?)?.unwrap(), cb),
frag => unimplemented!("push/{frag:?}")
}
}),
Some("exact") => eval_xy!(
"exact", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_exact, w_exact, h_exact
),
Some("min") => eval_xy!(
"min", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_min, w_min, h_min
),
Some("max") => eval_xy!(
"max", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_max, w_max, h_max
),
Some("push") => eval_xy!(
"push", expr, head, output, state, frags.next(), arg0, arg1, arg2, xy_push, x_push, y_push
),
_ => return Ok(false)
};
}?;
Ok(true)
}
/// Interpret TUI-specific layout operation.
///
/// ```
/// use tengri::{Namespace, Interpret, Tui, ratatui::prelude::Color};
/// use tengri::{lang::{Namespace, Interpret}, term::Tui, ratatui::prelude::Color};
///
/// struct State;
/// impl<'b> Namespace<'b, bool> for State {}
@ -189,8 +194,6 @@ pub fn eval_view_tui <'a, S> (
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
match frags.next() {
Some("text") => {