Compare commits

...

3 Commits

Author SHA1 Message Date
anna c0765b7dd0
[broken] major refactor, part 1
This commit introduces the first theoretical
concept for how custom components could work in
this framework, in combination with derive macros
for native and custom components.  The latter
still don't work because rustc can't infer the
Backend type parameter for custom components for
some reason.  I'm only committing this because
the diff is large enough as it is and i thought
it's a pretty good milestone for documenting the
development of this framework.  Furthermore, i'm
experiencing critical levels of being an eepy
little girl,
7 months ago
anna 3173e99797
dsl: support unnamed parameter lists
i stll don't really know my way around syn and
i'm probably using it wrong.  but then again,
this entire project is kind of an abomination
that shouldn't be used by anyone.
7 months ago
anna 9c060164ae
dsl: switch to syn 7 months ago

44
Cargo.lock generated

@ -2,13 +2,53 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "proc-macro2"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "uwui"
version = "0.1.0"
dependencies = [
"uwui_dsl",
"uwui-dsl",
]
[[package]]
name = "uwui_dsl"
name = "uwui-dsl"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

@ -1,7 +1,18 @@
[package]
name = "uwui"
version = "0.1.0"
edition.workspace = true
version.workspace = true
repository.workspace = true
[workspace]
members = [
"dsl"
]
[workspace.package]
edition = "2021"
repository = "https://git.bsd.gay/fef/uwui"
version = "0.1.0"
[dependencies]
uwui_dsl = { path = "./uwui_dsl" }
uwui-dsl = { path = "./dsl" }

@ -0,0 +1,13 @@
[package]
name = "uwui-dsl"
edition.workspace = true
version.workspace = true
repository.workspace = true
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.67"
quote = "1.0.33"
syn = "2.0.37"

@ -0,0 +1,45 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn invoke(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let expanded = quote! {
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::custom::CustomComponent<B> for #name
{}
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::Construct<B> for #name
{
type Params = <Self as crate::view::custom::NewCustom<B>>::Params;
fn construct(params: Self::Params) -> Self {
<Self as crate::view::custom::NewCustom<B>>::new(params)
}
fn into_view(self) -> crate::view::SomeView<B> {
crate::view::SomeView::<B>::Custom(Box::new(self))
}
}
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::ConstructGroup<B> for #name
where
#name : crate::view::custom::NewCustomGroup<B>,
{
fn construct_group<'a, I>(params: Self::Params, children: I) -> Self
where
I: Iterator<Item = &'a crate::view::View<B>>,
{
<Self as crate::view::custom::NewCustomGroup<B>>::new_group(params, children)
}
}
};
expanded.into()
}

@ -0,0 +1,2 @@
pub mod custom_component;
pub mod native_component;

@ -0,0 +1,49 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn invoke(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let expanded = quote! {
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::NativeComponent<B> for #name
where
#name : crate::backend::Render<B>,
{}
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::Construct<B> for #name
where
#name : crate::backend::Render<B>,
{
type Params = <Self as crate::view::NewNative<B>>::Params;
fn construct(params: Self::Params) -> Self {
<Self as crate::view::NewNative<B>>::new(params)
}
fn into_view(self) -> crate::view::SomeView<B> {
crate::view::SomeView::<B>::Native(Box::new(self))
}
}
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::ConstructGroup<B> for #name
where
#name : crate::view::NewNativeGroup<B> + crate::view::Render<B>,
{
fn construct_group<'a, I>(params: Self::Params, children: I) -> Self
where
I: Iterator<Item = &'a View<B>>,
{
<Self as crate::view::NewNativeGroup<B>>::new_group(params, children)
}
}
};
expanded.into()
}

@ -0,0 +1,21 @@
#![feature(proc_macro_diagnostic)]
use proc_macro::TokenStream;
mod derive;
mod template;
#[proc_macro]
pub fn template(input: TokenStream) -> TokenStream {
template::invoke(input)
}
#[proc_macro_derive(NativeComponent)]
pub fn native_component(input: TokenStream) -> TokenStream {
derive::native_component::invoke(input)
}
#[proc_macro_derive(CustomComponent)]
pub fn custom_component(input: TokenStream) -> TokenStream {
derive::custom_component::invoke(input)
}

@ -0,0 +1,70 @@
use crate::template::Params;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use super::{Body, Component, NamedParam};
impl ToTokens for Body {
fn to_tokens(&self, tokens: &mut TokenStream) {
let elements = self.elements.iter();
let code = quote! {
#(#elements),*
};
tokens.extend(code);
}
}
impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = &self.name;
let params = match self.params.as_ref() {
Some(p) => match p {
Params::Anon(params) => quote! {
::core::convert::Into::<<#name as crate::view::Construct<_>>::Params>::into(
(#(::core::convert::Into::<_>::into(#params)),*,)
)
},
Params::Named(params) => quote! {
{
let mut __uwui_params = <
<#name as crate::view::Construct::<_>>::Params as ::core::default::Default
>::default();
#(__uwui_params.#params)*
__uwui_params
}
},
},
None => quote! {
<<#name as crate::view::Construct<_>>::Params as ::core::default::Default>::default()
},
};
let initializer = if let Some(body) = self.body.as_ref() {
let body = body.elements.as_slice();
quote! {
crate::view::View::<_>::new_group::<#name, _>(
#params,
[
#(#body),*
]
)
}
} else {
quote! {
crate::view::View::<_>::new::<#name>(#params)
}
};
tokens.extend(initializer);
}
}
impl ToTokens for NamedParam {
fn to_tokens(&self, tokens: &mut TokenStream) {
let (name, val) = (&self.name, &self.val);
let code = quote! {
#name = ::core::convert::Into::<_>::into(#val);
};
tokens.extend(code);
}
}

@ -0,0 +1,50 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, token, Expr, Ident, Path};
mod compile;
mod parse;
pub fn invoke(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Body);
let output = quote! {
match (()) {
(()) => {
<
crate::view::Template as ::core::iter::FromIterator<crate::view::View<_>>
>::from_iter([
#input
])
}
}
};
output.into()
}
struct Body {
elements: Vec<Component>,
}
struct Component {
name: Path,
params: Option<Params>,
body: Option<Body>,
}
enum Params {
/// `name: value` style parameter list
Named(Vec<NamedParam>),
/// tuple style unnamed parameter list
Anon(Vec<Expr>),
}
struct NamedParam {
pub name: Ident,
pub colon: token::Colon,
pub val: Expr,
}
struct EventHandler {
pub name: Ident,
pub callback: Expr,
}

@ -0,0 +1,80 @@
use crate::template::Params;
use syn::{
braced, parenthesized,
parse::{discouraged::Speculative, Parse, ParseStream},
token, Expr, Path, Token,
};
use super::{Body, Component, NamedParam};
impl Parse for Body {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut elements = Vec::new();
while !input.is_empty() {
elements.push(input.parse()?);
}
Ok(Self { elements })
}
}
impl Parse for Component {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: Path = input.parse()?;
let params = if input.lookahead1().peek(token::Paren) {
Some(Params::parse(input)?)
} else {
None
};
let body = if input.lookahead1().peek(token::Brace) {
let inner;
braced!(inner in input);
Some(inner.parse()?)
} else {
None
};
Ok(Self { name, params, body })
}
}
impl Parse for Params {
fn parse(input: ParseStream) -> syn::Result<Self> {
let inner;
parenthesized!(inner in input);
if inner.is_empty() {
Ok(Self::Named(Vec::new()))
} else {
let ahead = inner.fork();
match ahead.parse_terminated(NamedParam::parse, Token![,]) {
Ok(vals) => {
let x = Ok(Self::Named(vals.into_iter().collect()));
inner.advance_to(&ahead);
x
}
Err(_) => {
let vals = inner.parse_terminated(Expr::parse, Token![,])?;
Ok(Self::Anon(vals.into_iter().collect()))
}
}
}
}
}
impl Parse for NamedParam {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: syn::Ident = input.parse()?;
if name.to_string().starts_with("__uwui") {
// TODO: we need to walk through the entire syntax tree, not just param names
return Err(syn::Error::new(
name.span(),
"Names starting with `__uwui` are reserved",
));
}
let colon = input.parse()?;
let val = input.parse()?;
Ok(Self { name, colon, val })
}
}

@ -0,0 +1,30 @@
use proc_macro::{Diagnostic, Level, Span};
use proc_macro2::TokenStream;
pub trait Compile: Sized {
fn compile(self) -> TokenStream;
}
pub fn emit_err(span: impl Into<Option<Span>>, msg: impl Into<String>) {
emit_diagnostic(span.into(), Level::Error, msg)
}
pub fn emit_warning(span: impl Into<Option<Span>>, msg: impl Into<String>) {
emit_diagnostic(span.into(), Level::Warning, msg)
}
pub fn emit_note(span: impl Into<Option<Span>>, msg: impl Into<String>) {
emit_diagnostic(span.into(), Level::Note, msg)
}
pub fn emit_help(span: impl Into<Option<Span>>, msg: impl Into<String>) {
emit_diagnostic(span.into(), Level::Help, msg)
}
fn emit_diagnostic(span: Option<Span>, level: Level, msg: impl Into<String>) {
let diagnostic = match span {
Some(span) => Diagnostic::spanned(span, level, msg),
None => Diagnostic::new(level, msg),
};
diagnostic.emit();
}

@ -3,22 +3,34 @@
/// You won't see any instances of this trait as it exists purely within the
/// type system. Its sole purpose is to differentiate between individual
/// [`Render`] implementations for the respective UI backends.
pub trait Backend: 'static {
pub trait Backend: Send + 'static {
/// Parameter passed to [`Render::render()`].
type Context;
/// Return type of [`Render::render()`].
type Result;
/// Successful return type for [`Render::render()`].
type Output: Combine;
/// Error return type for [`Render::render()`]
type Error;
/// The backend's name (for debug messages).
/// Should be all lowercase and match the name of the feature flag.
fn name() -> &'static str;
}
pub type RenderResult<B> = Result<Option<<B as Backend>::Output>, <B as Backend>::Error>;
pub trait Combine: Sized {
fn combine(self, other: Self) -> Self;
}
impl Combine for () {
fn combine(self, _other: Self) -> Self {}
}
/// A UI component that can be rendered with a backend.
pub trait Render<B: Backend> {
/// Render this component in the context provided by the backend.
fn render(&self, context: B::Context) -> B::Result;
fn render(&self, context: &mut B::Context) -> RenderResult<B>;
}
/// The backend we're compiling against.
@ -28,7 +40,8 @@ pub type TargetBackend = StubBackend;
pub struct StubBackend;
impl Backend for StubBackend {
type Context = ();
type Result = ();
type Output = ();
type Error = ();
fn name() -> &'static str {
"stub"
@ -41,7 +54,9 @@ impl Backend for StubBackend {
macro_rules! stub_render_impl {
($name:ident) => {
impl Render<StubBackend> for crate::view::$name {
fn render(&self, _context: ()) {}
fn render(&self, _context: &mut ()) -> RenderResult<StubBackend> {
Ok(Some(()))
}
}
};
}

@ -0,0 +1,15 @@
/// Specifies the alignment of a view's content.
#[derive(Default)]
pub enum Gravity {
/// Align content to the start of the view.
/// In horizontal layouts, this is the left side for L2R layouts and the
/// right side for R2L layouts. In vertical layouts, it is the top.
#[default]
Start,
/// Align content to the center of the view.
Center,
/// Align content to the end of the view.
/// In horizontal layouts, this is the right side for L2R layouts and the
/// left side for R2L layouts. In vertical layouts, it is the bottom.
End,
}

@ -1,15 +1,43 @@
pub mod backend;
pub mod layout;
pub mod view;
pub use uwui_dsl::template;
pub use uwui_dsl::{template, CustomComponent, NativeComponent};
use view::{stack::Gravity, Button, DynView, Text, VStack};
use layout::Gravity;
use view::{Button, GetTemplate, NewCustom, Template, Text, VStack};
fn asdf() -> Vec<DynView> {
template!(
VStack(gravity: Gravity::Center, homogay: true) {
Text(content: "hello, world")
fn asdf() -> Template {
template! {
VStack(Gravity::Center) {
MyComponent(x: 420)
Button(label: "don't click me")
}
)
}
}
#[derive(Default)]
struct MyParams {
x: i32,
}
#[derive(CustomComponent)]
struct MyComponent {
params: MyParams,
}
impl<B: backend::Backend> NewCustom<B> for MyComponent {
type Params = MyParams;
fn new(params: Self::Params) -> Self {
Self { params }
}
}
impl<B: backend::Backend> GetTemplate<B> for MyComponent {
fn get_template(&self) -> Template {
template! {
Text(format!("x = {}", self.params.x))
}
}
}

@ -1,8 +1,10 @@
use crate::backend::{Backend, Render};
use crate::view::{DynView, Id, NewView, View};
use uwui_dsl::NativeComponent;
use crate::backend::Backend;
use crate::view::{NativeComponent, NewNative};
#[derive(NativeComponent)]
pub struct Button {
id: Id,
params: ButtonParams,
}
@ -11,26 +13,13 @@ pub struct ButtonParams {
pub label: String,
}
impl<B: Backend> View<B> for Button
where
Button: Render<B>,
{
fn id(&self) -> Id {
self.id
}
}
impl<B: Backend> NewView<B> for Button
impl<B: Backend> NewNative<B> for Button
where
Button: Render<B>,
Button: NativeComponent<B>,
{
type Params = ButtonParams;
fn new(id: Id, params: Self::Params) -> Self {
Self { id, params }
}
fn into_view(self) -> DynView<B> {
Box::new(self)
fn new(params: Self::Params) -> Self {
Self { params }
}
}

@ -0,0 +1,51 @@
use std::any::Any;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use crate::backend::{Backend, Render, TargetBackend};
use crate::view::{Construct, SomeView, Template, View};
pub trait NewCustom<B: Backend = TargetBackend>: Sized {
type Params: Default;
fn new(params: Self::Params) -> Self;
}
pub trait NewCustomGroup<B: Backend = TargetBackend>: NewCustom<B> {
fn new_group<'a, I>(params: Self::Params, children: I) -> Self
where
I: IntoIterator<Item = &'a View<B>>;
}
pub trait GetTemplate<B: Backend = TargetBackend> {
fn get_template(&self) -> Template;
}
pub trait CustomComponent<B: Backend = TargetBackend>: Any + Send + GetTemplate {}
pub struct ComponentWrapper<C: CustomComponent<B>, B: Backend = TargetBackend> {
component: C,
_backend: PhantomData<B>,
}
impl<C, B> Deref for ComponentWrapper<C, B>
where
B: Backend,
C: CustomComponent<B>,
{
type Target = C;
fn deref(&self) -> &Self::Target {
&self.component
}
}
impl<C, B> DerefMut for ComponentWrapper<C, B>
where
B: Backend,
C: CustomComponent<B>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.component
}
}

@ -3,93 +3,147 @@ use std::{
sync::atomic::{AtomicU64, Ordering},
};
use crate::backend::{Backend, Render, TargetBackend};
#[cfg(doc)]
use crate::template;
use crate::backend::{Backend, Combine, Render, RenderResult, TargetBackend};
/// Convenience type for a pointer to an abstract node in the view tree.
pub type DynView<B = TargetBackend> = Box<dyn View<B>>;
pub struct View<B: Backend = TargetBackend> {
id: Id,
view: SomeView<B>,
children: Option<Vec<Self>>,
}
/// Globally unique ID for any instance of [`View`].
/// No ID will be issued twice for the entire lifetime of the application,
/// even IDs of views that have been destroyed. You shouldn't make any
/// assumptions about this type beyond that.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Id(u64);
pub enum SomeView<B: Backend> {
Native(Box<dyn NativeComponent<B>>),
Custom(Box<dyn CustomComponent<B>>),
}
pub trait Construct<B: Backend>: Sized {
type Params: Default;
fn construct(params: Self::Params) -> Self;
fn into_view(self) -> SomeView<B>;
}
pub trait ConstructGroup<B: Backend>: Construct<B> {
fn construct_group<'a, I>(params: Self::Params, children: I) -> Self
where
I: Iterator<Item = &'a View<B>> + 'a;
}
impl<B: Backend> View<B> {
pub fn new<T: Construct<B>>(params: T::Params) -> Self {
Self {
id: Id::new(),
view: T::construct(params).into_view(),
children: None,
}
}
pub fn new_group<T, I>(params: T::Params, children: I) -> Self
where
T: ConstructGroup<B>,
I: IntoIterator<Item = Self>,
{
let children: Vec<_> = children.into_iter().collect();
Self {
id: Id::new(),
view: T::construct_group(params, children.iter()).into_view(),
children: Some(children),
}
}
/// Base trait for any object representing a UI element.
pub trait View<B: Backend = TargetBackend>: Any + Send + Render<B> {
/// Get this view's globally unique ID.
fn id(&self) -> Id;
pub fn id(&self) -> Id {
self.id
}
}
/// Base trait for any [`View`] that can have children.
pub trait ViewGroup<B: Backend = TargetBackend>: View<B> {
/// Search for the child with the specified id and,
/// if found, return an immutable reference to it.
fn child(&self, id: Id) -> Option<&dyn View<B>>;
impl<B: Backend> Render<B> for View<B> {
fn render(&self, context: &mut B::Context) -> RenderResult<B> {
match &self.view {
SomeView::Native(native) => native.render(context),
SomeView::Custom(wrapper) => todo!(),
}
}
}
/// Search for the child with the specified id and,
/// if found, return a mutable reference to it.
fn child_mut(&mut self, id: Id) -> Option<&mut dyn View<B>>;
pub struct Template<B: Backend = TargetBackend> {
content: Vec<View<B>>,
}
/// Append a new child to this group.
fn add_child(&mut self, child: DynView<B>);
impl<B: Backend> FromIterator<View<B>> for Template<B> {
fn from_iter<T: IntoIterator<Item = View<B>>>(iter: T) -> Self {
Self {
content: iter.into_iter().collect(),
}
}
}
/// Remove a child from this group.
/// Returns the removed child, if any.
fn del_child(&mut self, id: Id) -> Option<DynView<B>>;
impl<B: Backend> Render<B> for Template<B>
where
View<B>: Render<B>,
{
fn render(&self, context: &mut B::Context) -> RenderResult<B> {
let mut result: Option<B::Output> = None;
for view in &self.content {
if let Some(x) = view.render(context)? {
result = Some(match result {
Some(result) => result.combine(x),
None => x,
})
}
}
Ok(result)
}
}
/// Template macros use this trait as a standardized way to construct new views
/// and pass parameters to them.
pub trait NewView<B: Backend = TargetBackend>: Sized {
/// Parameters for the constructor.
/// Since this is meant to be instantiated from within template macros,
/// it must be either a struct with all public fields (for named parameters)
/// or a tuple (for unnamed parameters).
type Params: Default + Sized;
/// Create a new instance of this element.
/// This isn't meant to be called manually; use [`template!`] instead.
fn new(id: Id, params: Self::Params) -> Self;
/// Turn this element into a view object, discarding the type information.
/// This is the last method that the template macro calls when instantiating
/// new components. Composed elements expand themselves into a view tree
/// in this step. Not meant to be called manually; use [`template!`].
fn into_view(self) -> DynView<B>;
pub trait NativeComponent<B: Backend = TargetBackend>: Any + Send + Render<B> {}
pub trait NewNative<B: Backend = TargetBackend>: NativeComponent<B> + Sized {
type Params: Default;
fn new(params: Self::Params) -> Self;
}
pub trait NewViewGroup<B: Backend = TargetBackend>: NewView<B> {
/// Like [`NewView::new()`], but with a list of children.
fn new_group(id: Id, params: Self::Params, children: Vec<DynView<B>>) -> Self;
pub trait NewNativeGroup<B: Backend = TargetBackend>: NewNative<B> {
fn new_group<'a, I>(params: Self::Params, children: I) -> Self
where
I: Iterator<Item = &'a View<B>>;
}
static ID_COUNTER: AtomicU64 = AtomicU64::new(Id::SENTINEL + 1);
/// Globally unique ID for any instance of [`View`].
/// No ID will be issued twice for the entire lifetime of the application,
/// even IDs of views that have been destroyed. You shouldn't make any
/// assumptions about this type beyond that.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Id(u64);
static ID_COUNTER: AtomicU64 = AtomicU64::new(Id::SENTINEL);
impl Id {
const SENTINEL: u64 = 0;
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let id = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
// it seems VERY unlikely that this ever overflows, but just in case
assert_ne!(id, Self::SENTINEL);
let id = ID_COUNTER
.fetch_add(1, Ordering::Relaxed)
.checked_add(1)
.expect("ID counter should never overflow");
Self(id)
}
pub const fn sentinel() -> Self {
pub(crate) const fn sentinel() -> Self {
Self(Self::SENTINEL)
}
pub const fn is_sentinel(&self) -> bool {
pub(crate) const fn is_sentinel(&self) -> bool {
self.0 == Self::SENTINEL
}
}
pub mod custom;
pub use custom::*;
pub mod button;
pub use button::Button;

@ -1,13 +1,8 @@
use crate::backend::{Backend, Render, TargetBackend};
use crate::view::{DynView, Id, NewView, NewViewGroup, View, ViewGroup};
use uwui_dsl::NativeComponent;
#[derive(Default)]
pub enum Gravity {
Start,
#[default]
Center,
End,
}
use crate::backend::Backend;
use crate::layout::Gravity;
use crate::view::{NativeComponent, NewNative, NewNativeGroup, View};
#[derive(Default)]
pub struct StackParams {
@ -15,80 +10,42 @@ pub struct StackParams {
pub homogay: bool,
}
impl From<(Gravity,)> for StackParams {
fn from(value: (Gravity,)) -> Self {
Self {
gravity: value.0,
..Default::default()
}
}
}
macro_rules! stack_impl {
($name:ident) => {
pub struct $name<B: Backend = TargetBackend> {
id: Id,
#[derive(NativeComponent)]
pub struct $name {
params: StackParams,
children: Vec<DynView<B>>,
}
impl<B: Backend> View<B> for $name<B>
where
$name<B>: Render<B>,
{
fn id(&self) -> Id {
self.id
}
}
impl<B: Backend> ViewGroup<B> for $name<B>
impl<B: Backend> NewNative<B> for $name
where
$name<B>: Render<B>,
{
fn child(&self, id: Id) -> Option<&dyn View<B>> {
self.children
.iter()
.find_map(|child| (child.id() == id).then_some(child.as_ref()))
}
fn child_mut(&mut self, id: Id) -> Option<&mut dyn View<B>> {
self.children
.iter_mut()
.find_map(|child| (child.id() == id).then_some(child.as_mut()))
}
fn add_child(&mut self, child: DynView<B>) {
self.children.push(child);
}
fn del_child(&mut self, id: Id) -> Option<DynView<B>> {
self.children
.iter()
.position(|child| child.id() == id)
.map(|index| self.children.remove(index))
}
}
impl<B: Backend> NewView<B> for $name<B>
where
$name<B>: Render<B>,
$name: NativeComponent<B>,
{
type Params = StackParams;
fn new(id: Id, params: Self::Params) -> Self {
Self {
id,
params,
children: Vec::new(),
}
}
fn into_view(self) -> DynView<B> {
Box::new(self)
fn new(params: Self::Params) -> Self {
Self { params }
}
}
impl<B: Backend> NewViewGroup<B> for $name<B>
impl<B: Backend> NewNativeGroup<B> for $name
where
$name<B>: Render<B>,
$name: NativeComponent<B>,
{
fn new_group(id: Id, params: Self::Params, children: Vec<DynView<B>>) -> Self {
Self {
id,
params,
children,
}
fn new_group<'a, I>(params: Self::Params, _children: I) -> Self
where
I: Iterator<Item = &'a View<B>>,
{
Self { params }
}
}
};

@ -1,39 +1,36 @@
use crate::backend::{Backend, Render};
use crate::view::{DynView, Id, NewView, View};
use uwui_dsl::NativeComponent;
use crate::backend::Backend;
use crate::layout::Gravity;
use crate::view::{NativeComponent, NewNative};
#[derive(NativeComponent)]
pub struct Text {
id: Id,
content: String,
params: TextParams,
}
#[derive(Default)]
pub struct TextParams {
pub content: String,
pub gravity: Gravity,
}
impl<B: Backend> View<B> for Text
where
Text: Render<B>,
{
fn id(&self) -> Id {
self.id
impl From<(String,)> for TextParams {
fn from(value: (String,)) -> Self {
Self {
content: value.0,
..Default::default()
}
}
}
impl<B: Backend> NewView<B> for Text
impl<B: Backend> NewNative<B> for Text
where
Text: Render<B>,
Text: NativeComponent<B>,
{
type Params = TextParams;
fn new(id: Id, params: Self::Params) -> Self {
Self {
id,
content: params.content,
}
}
fn into_view(self) -> DynView<B> {
Box::new(self)
fn new(params: Self::Params) -> Self {
Self { params }
}
}

@ -1,7 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "uwui_dsl"
version = "0.1.0"

@ -1,7 +0,0 @@
[package]
name = "uwui_dsl"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true

@ -1,14 +0,0 @@
; This grammar spec uses the ABNF notation as defined in RFC 5234.
template = space *component
component = *listener name space [ param-list space ] [ child-list space ]
listener = "@" ident space "(" space expr space ")" space
name = [ path-sep space ] ident *( space path-sep space ident )
path-sep = "::"
param-list = "(" space *( param space "," space ) [ param space ] ")"
param = ident space ":" space expr
child-list = "{" space *( component ) "}"
expr = <now that's just rust code, stop being a pedantic bitch about it>
ident = <any valid rust identifier, excluding those starting with "__uwui">
space = *( <anything that rust considers whitespace, including comments> )

@ -1,215 +0,0 @@
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, TokenStream, TokenTree};
use crate::parser::{Component, Name, Param};
use crate::TtIter;
impl Component {
pub(crate) fn compile(self) -> impl TtIter {
// wrap into `<Name as $crate::view::NewView<_>>::into_view()`
self.name
.clone()
.into_new_view()
.chain(syms::parse("::into_view"))
.chain([TokenTree::from(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter(self.compile_inner()),
))])
}
pub(crate) fn compile_inner(self) -> impl TtIter {
let span = self.name.span();
self.name
.clone()
.into_constructor(self.children.is_some())
.chain([Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter(
syms::id_new_call(span)
.chain([
Punct::new(',', Spacing::Alone).into(),
Self::compile_params(self.name, self.params),
])
.chain(self.children.into_iter().flat_map(|children| {
syms::parse(", ::std::vec::Vec::<_>::from").chain([Group::new(
Delimiter::Parenthesis,
Self::compile_children(children).into(),
)
.into()])
})),
),
)
.into()])
}
/// `Name(key1: val1)` ->
///
/// ```
/// {
/// let mut __uwui_params = <Name as ::uwui::view::NewView>::Params::default();
/// __uwui_params.key1 = ::core::convert::Into::<_>::into(val1);
/// __uwui_params
/// }
/// ```
fn compile_params(name: Name, params: Option<Vec<Param>>) -> TokenTree {
let span = name.span();
Group::new(
Delimiter::Brace,
TokenStream::from_iter(
[
TokenTree::Ident(Ident::new("let", span)),
Ident::new("mut", span).into(),
Ident::new("__uwui_params", span).into(),
Punct::new('=', Spacing::Alone).into(),
]
.into_iter()
.chain(name.into_params_type())
.chain([
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new("default", span).into(),
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
Punct::new(';', Spacing::Alone).into(),
])
.chain(
params
.unwrap_or(Vec::new())
.into_iter()
.flat_map(|param| param.compile(Ident::new("__uwui_params", span))),
)
.chain([Ident::new("__uwui_params", span).into()]),
),
)
.into()
}
fn compile_children(children: Vec<Component>) -> TokenTree {
Group::new(
Delimiter::Bracket,
TokenStream::from_iter(children.into_iter().flat_map(|child| {
child
.compile()
.chain([Punct::new(',', Spacing::Alone).into()])
})),
)
.into()
}
}
impl Name {
/// `Name -> <Name as $crate::view::NewView<_>>::new` (!group)
/// `Name -> <Name as $crate::view::NewViewGroup<_>>::new_group` (group)
fn into_constructor(self, group: bool) -> impl TtIter {
let span = self.span();
let trait_name = if group { "NewViewGroup" } else { "NewView" };
let fn_name = if group { "new_group" } else { "new" };
[TokenTree::Punct(Punct::new('<', Spacing::Alone))]
.into_iter()
.chain(self.0.into_iter())
.chain([Ident::new("as", span).into()])
.chain(syms::absolute_path(span, ["view", trait_name].into_iter()))
.chain([
Punct::new('<', Spacing::Alone).into(),
Ident::new("_", span).into(),
Punct::new('>', Spacing::Joint).into(),
Punct::new('>', Spacing::Joint).into(),
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new(fn_name, span).into(),
])
}
/// `Name -> <Name as $crate::view::NewView::<_>>::Params`
fn into_params_type(self) -> impl TtIter {
let span = self.span();
self.into_new_view().chain([
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new("Params", span).into(),
])
}
/// `Name -> <Name as $crate::view::NewView<_>>`
fn into_new_view(self) -> impl TtIter {
let span = self.span();
[Punct::new('<', Spacing::Alone).into()]
.into_iter()
.chain(self.0.into_iter())
.chain([Ident::new("as", span).into()])
.chain(syms::new_view(span))
.chain([Punct::new('>', Spacing::Joint).into()])
}
}
impl Param {
/// `name: expr -> ident.name = ::core::convert::Into::<_>::into(expr);`
fn compile(self, ident: Ident) -> impl TtIter {
let (name, colon, expr) = (self.0, self.1, self.2);
let mut eq = Punct::new('=', Spacing::Alone);
eq.set_span(colon.span());
[
ident.into(),
Punct::new('.', Spacing::Alone).into(),
name,
eq.into(),
]
.into_iter()
.chain(syms::parse("::core::convert::Into::<_>::into"))
.chain([
Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter(expr.0.into_iter()),
)
.into(),
Punct::new(';', Spacing::Alone).into(),
])
}
}
/// Helpers for static identifiers within our API
mod syms {
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream};
use std::str::FromStr;
use crate::TtIter;
/// `$crate::view::Id::new()`
pub(super) fn id_new_call(span: Span) -> impl TtIter {
absolute_path(span, ["view", "Id", "new"].into_iter()).chain([Group::new(
Delimiter::Parenthesis,
TokenStream::new(),
)
.into()])
}
/// `$crate::view::NewView::<_>`
pub(super) fn new_view(span: Span) -> impl TtIter {
absolute_path(span, ["view", "NewView"].into_iter()).chain(parse("::<_>"))
}
pub(super) fn absolute_path<'a>(
span: Span,
segments: impl Iterator<Item = &'a str> + 'a,
) -> impl TtIter + 'a {
[Ident::new("crate", span).into()]
.into_iter()
.chain(segments.flat_map(move |name| {
[
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new(name, span).into(),
]
.into_iter()
}))
}
pub(super) fn parse(code: &str) -> impl TtIter {
TokenStream::from_str(code).unwrap().into_iter()
}
}

@ -1,36 +0,0 @@
#![feature(proc_macro_diagnostic)]
#![feature(proc_macro_span)]
use proc_macro::{Delimiter, Group, Punct, Spacing, TokenStream, TokenTree};
use std::str::FromStr;
pub(crate) trait TtIter: Iterator<Item = TokenTree> {}
impl<T> TtIter for T where T: Iterator<Item = TokenTree> {}
mod compiler;
mod parser;
use parser::Parser;
/// Build a view tree out of a template written in the uwui DSL.
#[proc_macro]
pub fn template(input: TokenStream) -> TokenStream {
let mut stream = input.into_iter().peekable();
let parser = Parser::from(&mut stream);
let array = TokenTree::Group(Group::new(
Delimiter::Bracket,
TokenStream::from_iter(parser.flat_map(|component| {
component
.compile()
.chain([Punct::new(',', Spacing::Alone).into()])
})),
));
let mut output = TokenStream::from_str("::std::vec::Vec::<_>::from").unwrap();
output.extend(TokenStream::from(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
array.into(),
))));
output = TokenStream::from(TokenTree::Group(Group::new(Delimiter::Brace, output)));
output
}

@ -1,388 +0,0 @@
use crate::TtIter;
use proc_macro::{
Delimiter, Diagnostic, Group, Ident, Level, Spacing, Span, TokenStream, TokenTree,
};
use std::{fmt, iter::Peekable};
/// A single component definition, including children.
pub(crate) struct Component {
/// Event listeners attached to this component.
pub(crate) listeners: Vec<Listener>,
/// The type name of this component (may be namespaced).
pub(crate) name: Name,
/// The parameter list as a dictionary, if `name` is followed by parentheses.
pub(crate) params: Option<Vec<Param>>,
/// All child component definitions.
pub(crate) children: Option<Vec<Component>>,
}
#[derive(Clone)]
pub(crate) struct Name(pub(crate) Vec<TokenTree>);
pub(crate) struct Param(pub(crate) TokenTree, pub(crate) TokenTree, pub(crate) Expr);
pub(crate) struct Expr(pub(crate) Vec<TokenTree>);
pub(crate) struct Listener {
pub(crate) at: TokenTree,
pub(crate) name: TokenTree,
pub(crate) callback: TokenTree,
}
/// The main syntax tree parser. This implements [`Iterator`], so it will
/// only parse (and consume the input iterator) when consumed itself.
pub(crate) struct Parser<'a, T: TtIter> {
stream: &'a mut Peekable<T>,
}
impl<'a, T: TtIter> From<&'a mut Peekable<T>> for Parser<'a, T> {
fn from(stream: &'a mut Peekable<T>) -> Self {
Self { stream }
}
}
impl<'a, T: TtIter> Iterator for Parser<'a, T> {
type Item = Component;
fn next(&mut self) -> Option<Component> {
self.parse_component()
}
}
impl<'a, T: TtIter> Parser<'a, T> {
/// ```notrust
/// component = *listener name space [ param-list space ] [ child-list space ]
/// param-list = "(" space *( param space "," space ) [ param space ] ")"
/// child-list = "{" space *( component ) "}"
/// ```
fn parse_component(&mut self) -> Option<Component> {
self.stream.peek()?;
let mut listeners = Vec::new();
while let Some(listener) = Listener::parse(self.stream) {
Diagnostic::spanned(
listener.span(),
Level::Error,
"Event listeners aren't implemented yet",
)
.emit();
listeners.push(listener);
}
let name = Name::eat(self.stream)?;
let params = self
.stream
.next_if(is_group(Delimiter::Parenthesis))
.map(|tt| match tt {
TokenTree::Group(group) => group.stream().into_iter().peekable(),
_ => unreachable!("is_group() already matched this"),
})
.map(|mut stream| {
let mut list = Vec::new();
while let Some(param) = Param::parse(&mut stream) {
list.push(param);
stream.next_if(is_punct(','));
}
if let Some(tt) = stream.peek() {
Diagnostic::spanned(tt.span(), Level::Error, "Unexpected token").emit();
}
list
});
let children = self
.stream
.next_if(is_group(Delimiter::Brace))
.map(|tt| match tt {
TokenTree::Group(group) => group.stream().into_iter().peekable(),
_ => unreachable!("is_child_list already matched this"),
})
.map(|mut stream| Parser::from(&mut stream).collect());
Some(Component {
listeners,
name,
params,
children,
})
}
}
impl Name {
/// ```notrust
/// name = [ path-sep space ] ident *( space path-sep space ident )
/// ```
fn eat(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
stream.peek()?;
let mut segments = Vec::new();
// [ path-sep ]
segments.extend(
Self::parse_path_sep(stream)
.into_iter()
.flat_map(|sep| sep.into_iter()),
);
// ident
segments.push(eat_ident(stream)?);
// *( path-sep ident )
while let Some(sep) = Self::parse_path_sep(stream) {
segments.extend(sep.into_iter());
segments.push(eat_ident(stream)?);
}
Some(Name(segments))
}
pub(crate) fn span(&self) -> Span {
let first = self.0.first().unwrap().span();
let last = self.0.last().unwrap().span();
first.join(last).unwrap()
}
/// ```notrust
/// path-sep = "::"
/// ```
fn parse_path_sep(stream: &mut Peekable<impl TtIter>) -> Option<[TokenTree; 2]> {
let first = stream.next_if(is_punct_spaced(':', Spacing::Joint))?;
let second = match stream.next() {
Some(tt) => match &tt {
TokenTree::Punct(punct)
if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
{
Some(tt)
}
_ => {
Diagnostic::spanned(tt.span(), Level::Error, "Expected a `:`").emit();
None
}
},
None => {
Diagnostic::new(Level::Error, "Unexpected end of stream").emit();
None
}
}?;
Some([first, second])
}
}
impl Param {
/// ```notrust
/// param = ident space ":" space expr
/// ```
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
stream.peek()?;
let ident = eat_ident(stream)?;
let colon = match stream.next() {
Some(tt) => match &tt {
TokenTree::Punct(punct)
if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
{
Some(tt)
}
_ => {
Diagnostic::spanned(tt.span(), Level::Error, "Expected a `:`").emit();
None
}
},
None => {
Diagnostic::new(Level::Error, "Unexpected end of stream").emit();
None
}
}?;
let expr = Expr::parse(stream)?;
Some(Param(ident, colon, expr))
}
}
impl Expr {
/// ```notrust
/// expr = <now that's just rust code, stop being a pedantic bitch about it>
/// ```
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
let mut tokens = Vec::new();
while let Some(tt) = stream.peek() {
if is_punct(',')(tt) {
break;
} else {
tokens.push(check_ident_recurse(
stream.next().expect("We already peeked this"),
));
}
}
if tokens.is_empty() {
None
} else {
Some(Expr(tokens))
}
}
}
impl Listener {
/// ```notrust
/// listener = "@" ident space "(" space expr space ")" space
/// ```
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
let at = stream.next_if(is_punct('@'))?;
let name = eat_ident(stream)?;
let callback = match stream.next_if(is_group(Delimiter::Parenthesis)) {
Some(tt) => tt,
None => {
Diagnostic::spanned(
at.span().join(name.span()).unwrap(),
Level::Error,
"Event listeners require a callback",
)
.emit();
return None;
}
};
Some(Self { at, name, callback })
}
fn span(&self) -> Span {
self.at.span().join(self.callback.span()).unwrap()
}
}
/// ```notrust
/// ident = <any valid rust identifier, excluding those starting with "__uwui">
/// ```
fn eat_ident(stream: &mut Peekable<impl TtIter>) -> Option<TokenTree> {
match stream.next() {
Some(tt) => match &tt {
TokenTree::Ident(i) => {
check_ident(i);
Some(tt)
}
_ => {
Diagnostic::spanned(tt.span(), Level::Error, "Expected an identifier").emit();
None
}
},
None => {
Diagnostic::new(Level::Error, "Unexpected end of stream").emit();
None
}
}
}
fn check_ident_recurse(tt: TokenTree) -> TokenTree {
match tt {
TokenTree::Ident(ref ident) => {
check_ident(ident);
tt
}
TokenTree::Group(group) => {
let mut wrapped = Group::new(
group.delimiter(),
TokenStream::from_iter(group.stream().into_iter().map(check_ident_recurse)),
);
wrapped.set_span(group.span());
TokenTree::from(wrapped)
}
_ => tt,
}
}
fn check_ident(ident: &Ident) {
if ident.to_string().starts_with("__uwui") {
Diagnostic::spanned(
ident.span(),
Level::Error,
"Identifiers starting with `__uwui` are reserved",
)
.emit();
}
}
const fn is_punct_spaced(c: char, s: Spacing) -> impl FnMut(&TokenTree) -> bool {
move |tt| match tt {
TokenTree::Punct(punct) => punct.as_char() == c && punct.spacing() == s,
_ => false,
}
}
fn is_punct(c: char) -> impl FnMut(&TokenTree) -> bool {
move |tt| match tt {
TokenTree::Punct(punct) => punct.as_char() == c,
_ => false,
}
}
fn is_group(delim: Delimiter) -> impl FnMut(&TokenTree) -> bool {
move |tt| match tt {
TokenTree::Group(group) => group.delimiter() == delim,
_ => false,
}
}
///////////// debug stuff ///////////////
impl Component {
fn print(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
let indent = " ".repeat(depth);
f.write_str(&indent)?;
fmt::Debug::fmt(&self.name, f)?;
if let Some(params) = &self.params {
f.write_str("(")?;
let mut first = true;
for param in params {
if first {
first = false;
} else {
f.write_str(", ")?;
}
fmt::Debug::fmt(param, f)?;
}
f.write_str(")")?;
}
f.write_str("\n")?;
if let Some(children) = &self.children {
for child in children {
child.print(f, depth + 1)?;
}
}
Ok(())
}
}
impl fmt::Debug for Component {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("\n")?;
self.print(f, 0)?;
f.write_str("\n")
}
}
impl fmt::Debug for Name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for segment in &self.0 {
f.write_str(&segment.to_string())?;
}
Ok(())
}
}
impl fmt::Debug for Param {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0.to_string())?;
f.write_str(" => ")?;
fmt::Debug::fmt(&self.2, f)
}
}
impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for tt in &self.0 {
f.write_str(&tt.to_string())?;
}
Ok(())
}
}
Loading…
Cancel
Save