More widget stage0 code.

This commit is contained in:
Samuel Guerra 2021-01-18 20:38:37 -03:00
parent ad828a0ba4
commit 9509de2025
3 changed files with 238 additions and 16 deletions

View File

@ -99,14 +99,16 @@ pub trait VarObj<T: VarValue>: protected::Var + 'static {
/// References the current value.
fn get<'a>(&'a self, vars: &'a Vars) -> &'a T;
/// References the current value if it is new.
/// References the current value if it [is new](Self::is_new).
fn get_new<'a>(&'a self, vars: &'a Vars) -> Option<&'a T>;
/// If [`set`](Self::set) or [`modify`](Var::modify) was called in the previous update.
/// If [`set`](Self::set) or [`modify`](Var::modify) where called in the previous update.
///
/// When you set the variable, the new value is only applied after the UI tree finishes
/// the current update. The value is then applied causing a new update to happen, in the new
/// update this method returns `true`. After the new update it returns `false` again.
///
/// The new value can still be equal to the previous value, the variable does not check equality on assign.
fn is_new(&self, vars: &Vars) -> bool;
/// Version of the current value.
@ -134,6 +136,9 @@ pub trait VarObj<T: VarValue>: protected::Var + 'static {
/// Variables are not changed immediately, the full UI tree gets a chance to see the current value,
/// after the current UI update, the values set here are applied.
///
/// If the result is `Ok` the variable will be flagged as [new](Self::is_new) in the next update. Value
/// equality is not checked, setting to an equal value still flags a *new*.
///
/// ### Error
///
/// Returns [`VarIsReadOnly`] if [`is_read_only`](Self::is_read_only) is `true`.
@ -201,6 +206,9 @@ pub trait Var<T: VarValue>: VarObj<T> + Clone {
///
/// This is a variation of the [`set`](VarObj::set) method that does not require
/// an entire new value to be instantiated.
///
/// If the result is `Ok` the variable will be flagged as [new](Self::is_new) in the next update,
/// even if `change` does not do anything.
fn modify<F: FnOnce(&mut T) + 'static>(&self, vars: &Vars, change: F) -> Result<(), VarIsReadOnly>;
/// Returns the variable as a type that is [`always_read_only`](VarObj::always_read_only).
@ -326,9 +334,12 @@ pub trait IntoVar<T: VarValue>: Clone {
///
/// # Interpolation
///
/// Variable interpolation is done by quoting the variable with `#{<var-expr>}`. The braces are required
/// and the `<var-expr>` is evaluated before *capturing* starts so if you interpolate `#{var_a.clone()}` `var_a`
/// will still be available after the `var_expr` call.
/// Variable interpolation is done by quoting the variable with `#{<var-expr>}`, the braces are required.
///
/// The `<var-expr>` is evaluated before *capturing* starts so if you interpolate `#{var_a.clone()}` `var_a`
/// will still be available after the `var_expr` call. Equal `<var-expr>` only evaluate once.
///
/// The interpolation result value is the [`Var::get`] return value.
///
/// # Expansion
///

View File

@ -4,16 +4,16 @@ use proc_macro2::{TokenStream, TokenTree};
use quote::ToTokens;
use syn::{
braced,
parse::{Parse, ParseBuffer, ParseStream},
parse::{Parse, ParseStream},
parse2, parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
token, visit_mut, Attribute, FnArg, Ident, Item, ItemFn, ItemMacro, ItemMod, Path, Token, TypeTuple,
token, Attribute, FnArg, Ident, Item, ItemFn, ItemMacro, ItemMod, Path, Token,
};
use util::{non_user_braced_id, parse2_punctuated};
use crate::{
util::{self, crate_core, non_user_braced, non_user_bracketed, non_user_parenthesized, Attributes, Errors},
util::{self, non_user_braced, non_user_bracketed, non_user_parenthesized, Attributes, Errors},
widget_new2::{PropertyValue, When},
};
@ -108,9 +108,10 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
let mut captures = HashSet::new();
for capture in new_child.iter().chain(&new) {
if !captures.insert(capture) {
errors.push(format!("property `{}` already captured", capture), capture.span());
errors.push(format_args!("property `{}` already captured", capture), capture.span());
}
}
let captures = captures;
// generate `__new_child` and `__new` if new functions are defined in the widget.
let new_child__ = new_child_fn.as_ref().map(|_| {
@ -132,9 +133,99 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
}
}
});
// captured property existence validation happens "widget_2_declare.rs"
// process properties
let mut declared_properties = HashSet::new();
let mut built_properties = TokenStream::default();
let mut property_defaults = TokenStream::default();
let mut property_declarations = TokenStream::default();
let mut property_unsets = TokenStream::default();
for property in child_properties.iter().chain(properties.iter()) {
let p_ident = property.ident();
if !declared_properties.insert(p_ident) {
errors.push(format_args!("property `{}` is already declared", p_ident), p_ident.span());
continue;
}
// declare new capture properties.
if let Some((_, new_type)) = &property.type_ {
if !captures.contains(p_ident) {
// new capture properties must be captured by new *new* functions.
errors.push(
format_args!("property `{}` is declared in widget but is not captured by widget", p_ident),
p_ident.span(),
);
continue;
}
let p_mod_ident = ident!("__p_{}", p_ident);
let inputs = new_type.fn_input_tokens(p_ident);
property_declarations.extend(quote! {
#[doc(hidden)]
#[#crate_core::property(capture_only)]
pub fn #p_mod_ident(#inputs) -> ! { }
});
}
let mut default = false;
let mut required = false;
// process default value or special value.
if let Some((_, default_value)) = &property.value {
if let PropertyValue::Special(sp, _) = default_value {
if sp == "unset" {
if property.alias.is_some() || property.path.get_ident().is_some() {
// only single name path without aliases can be referencing an inherited property.
errors.push("can only unset inherited property", sp.span());
continue;
}
// the final inherit validation is done in "widget_2_declare.rs".
p_ident.to_tokens(&mut property_unsets);
} else if sp == "required" {
required = true;
} else {
// unknown special.
errors.push(format_args!("unexpected `{}!` in default value", sp), sp.span());
continue;
}
} else {
default = true;
let fn_ident = ident!("__d_{}", p_ident);
let p_mod_ident = ident!("__p_{}", p_ident);
let expr = default_value.expr_tokens(&quote! { self::#p_mod_ident });
property_defaults.extend(quote! {
#[doc(hidden)]
pub fn #fn_ident() -> impl self::#p_mod_ident::Args {
#expr
}
});
}
}
// all captures are required
//
// note: a property can become required in "widget_2_declare.rs" if the
// widget is inheriting *new* functions.
required |= captures.contains(p_ident);
built_properties.extend(quote! {
// TODO docs, cfg
#p_ident {
default #default,
required #required
}
});
}
drop(declared_properties);
// process whens
let mut built_whens = TokenStream::default();
let mut when_conditions = TokenStream::default();
let mut when_defaults = TokenStream::default();
for (i, when) in whens.into_iter().enumerate() {
// when condition with `self.property(.member)?` converted to `#(__property__member)` for the `expr_var` macro.
let condition = match syn::parse2::<WhenExprToVar>(when.condition_expr.to_token_stream()) {
Ok(c) => c,
Err(e) => {
@ -142,14 +233,63 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
continue;
}
};
let unique_props: HashSet<_> = condition.properties.iter().map(|(p, _)| p).collect();
// when ident, `__w{i}_{condition_expr_to_str}`
let ident = when.make_ident(i);
let input_idents = unique_props.iter().map(|p| ident!("__{}", p));
let prop_idents = unique_props.iter().map(|p| ident!("__p_{}", p));
let mut assigns = vec![];
for assign in when.assigns {
// property default value validation happens "widget_2_declare.rs"
let fields = condition.properties.iter().map(|(p, m)| ident!("__{}{}", p, m));
if let Some(property) = assign.path.get_ident() {
// validate property only assigned once in the when block.
if assigns.iter().any(|p| p == property) {
errors.push(
format_args!("property `{}` already assigned in this `when` block", property),
property.span(),
);
continue;
}
// validate value not one of the special commands (`unset!`, `required!`).
if let PropertyValue::Special(sp, _) = &assign.value {
errors.push(format_args!("`{}` not allowed in `when` block", sp), sp.span());
continue;
}
// ident of property module in the widget.
let prop_ident = ident!("__p_{}", property);
// ident of the property value function.
let fn_ident = ident!("{}__{}", ident, property);
assigns.push(property.clone());
let expr = assign.value.expr_tokens(&quote!(self::#prop_ident));
when_defaults.extend(quote! {
#[doc(hidden)]
pub fn #fn_ident() -> impl self::#prop_ident::Args {
#expr
}
});
} else {
let suggestion = &assign.path.segments.last().unwrap().ident;
errors.push(
format_args!("widget properties only have a single name, try `{}`", suggestion),
assign.path.span(),
);
}
}
// properties used in the when condition.
let inputs: HashSet<_> = condition.properties.iter().map(|(p, _)| p).collect();
// name of property inputs Args reference in the condition function.
let input_idents = inputs.iter().map(|p| ident!("__{}", p));
// name of property inputs in the widget module.
let prop_idents = inputs.iter().map(|p| ident!("__p_{}", p));
// name of the fields for each interpolated property
let field_idents = condition.properties.iter().map(|(p, m)| ident!("__{}{}", p, m));
let input_ident_per_field = condition.properties.iter().map(|(p, _)| ident!("__{}", p));
let members = condition.properties.iter().map(|(_, m)| m);
@ -158,12 +298,25 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
when_conditions.extend(quote! {
#[doc(hidden)]
pub fn #ident(#(#input_idents : &impl self::#prop_idents::Args),*) -> impl #crate_core::var::Var<bool> {
#(let #fields = #crate_core::var::IntoVar::into_var(std::clone::Clone::clone(#input_ident_per_field.#members())))*
#(let #field_idents = #crate_core::var::IntoVar::into_var(std::clone::Clone::clone(#input_ident_per_field.#members())))*
#crate_core::var::expr_var! {
#expr
}
}
});
let inputs = inputs.iter();
built_whens.extend(quote! {
// TODO attributes
#ident {
inputs {
#(#inputs),*
}
assigns {
#(#assigns),*
}
}
});
}
let r = quote! {
@ -178,15 +331,20 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
inherit { #(#inherits;)* }
widget {
module { #mod_path }
docs { #(#docs)* }
ident { #ident }
mixin { #mixin }
properties {
unset_properties {
#property_unsets
}
properties {
#built_properties
}
whens {
#built_whens
}
new_child { #(#new_child)* }
@ -203,7 +361,10 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
#new_child__
#new__
#property_defaults
#when_conditions
#when_defaults
}
}
}
@ -483,6 +644,15 @@ impl Parse for ItemProperty {
})
}
}
impl ItemProperty {
/// The property ident.
fn ident(&self) -> &Ident {
self.alias
.as_ref()
.map(|(_, id)| id)
.unwrap_or_else(|| &self.path.segments.last().unwrap().ident)
}
}
enum PropertyType {
/// `{ name: u32 }` OR `{ name: impl IntoVar<u32> }` OR `{ name0: .., name1: .. }`
@ -508,6 +678,21 @@ impl Parse for PropertyType {
}
}
}
impl PropertyType {
fn fn_input_tokens(&self, property: &Ident) -> TokenStream {
match self {
PropertyType::Named(_, fields) => fields.to_token_stream(),
PropertyType::Unnamed(unamed) => {
if unamed.len() == 1 {
quote! { #property: #unamed }
} else {
let names = (0..unamed.len()).map(|i| ident!("arg{}", i));
quote! { #(#names: #unamed),* }
}
}
}
}
}
struct NamedField {
ident: Ident,
@ -523,6 +708,13 @@ impl Parse for NamedField {
})
}
}
impl ToTokens for NamedField {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.ident.to_tokens(tokens);
self.colon.to_tokens(tokens);
self.ty.to_tokens(tokens);
}
}
mod keyword {
pub use crate::widget_new2::keyword::when;

View File

@ -488,6 +488,25 @@ impl PropertyValue {
_ => None,
}
}
/// Convert this value to an expr. Panics if `self` is [`Special`].
pub fn expr_tokens(&self, property_path: &TokenStream) -> TokenStream {
match self {
PropertyValue::Unnamed(args) => {
quote! {
#property_path::ArgsImpl::new(#args)
}
}
PropertyValue::Named(_, fields) => {
quote! {
#property_path::code_gen! { named_new #property_path {
#fields
}}
}
}
PropertyValue::Special(_, _) => panic!("cannot expand special"),
}
}
}
impl Parse for PropertyValue {
fn parse(input: ParseStream) -> syn::Result<Self> {