#[macro_export]
macro_rules! rsml {
($(#[$($attrs:tt)*])* pub($($vis:tt)*) struct $name:ident $(: $derive:ident)* {$($body:tt)*}) => {
rsml! { @parse_fields $(#[$($attrs)*])*, [pub($($vis)*)], $name $(: $derive)*, $($body)* }
};
($(#[$($attrs:tt)*])* pub struct $name:ident $(: $derive:ident)* {$($body:tt)*}) => {
rsml! { @parse_fields $(#[$($attrs)*])*, [pub], $name $(: $derive)*, $($body)* }
};
($(#[$($attrs:tt)*])* struct $name:ident $(: $derive:ident)* {$($body:tt)*}) => {
rsml! { @parse_fields $(#[$($attrs)*])*, [], $name $(: $derive)* , $($body)* }
};
(@parse_fields $(#[$attrs:meta])*, [$($vis:tt)*], $name:ident $(: $derive:ident)*,
$(@signal $signal:ident),* $(,)*
$( $field:ident : $typ:ty $(= $value:expr )* ),* $(,)*
$(; $($sub_items:tt)* )*
) => {
$(#[$attrs])* $($vis)* struct $name<'a> {
$( DeriveItem : ::std::rc::Rc<$derive<'a>> ,)*
$( pub $signal : $crate::properties::Signal<'a>, )*
$( pub $field : $crate::properties::Property<'a, $typ> ),*
}
impl<'a> $name<'a> {
pub fn new() -> ::std::rc::Rc<Self> {
$(#[allow(non_snake_case)] let $derive = rsml!(@init_derive $name $derive { $($sub_items)* }) ;)*
let r = ::std::rc::Rc::new(Self {
$( DeriveItem : $derive.0 ,)*
$( $signal: Default::default(), )*
$( $field: rsml!{@parse_default $($value)*} ),* }
);
$(
$derive.1.borrow_mut().$name = ::std::rc::Rc::downgrade(&r);
($derive.2)();
)*
$(rsml!{ @init_field r, $name, $field, $($value)* })*
r
}
}
$(
impl<'a> ::std::ops::Deref for $name<'a> {
type Target = $derive<'a>;
fn deref(&self) -> &Self::Target {
&self.DeriveItem
}
}
)*
};
(@init_field $r:ident, $name:ident, $field:ident $(. $field_cont:ident)* , $bind:expr) => {
{
let wr = ::std::rc::Rc::downgrade(&$r);
#[allow(unused_variables)]
#[allow(non_snake_case)]
$r.$field $(. $field_cont)* .set_binding(move || { let $name = wr.upgrade().unwrap(); $bind });
}
};
(@init_field $r:ident, $name:ident, $field:ident $(. $field_cont:ident)* ,) => { };
(@parse_default $($x:tt)*) => { Default::default() };
(@init_derive $parent:ident $name:ident { $($rest:tt)* }) => {
rsml!{@find_all_id (parse_as_declare_start {$parent $name, $($rest)*}) [] $name => $($rest)* }
};
(@parse_as_declare_start {$parent:ident $name:ident, $($rest:tt)*} [$(($ids:tt $ids_ty:ident))*]) => { {
#[derive(Default)]
#[allow(non_snake_case)]
struct IdsContainer<'a> {
$($ids: ::std::rc::Weak<$ids_ty<'a>> ,)*
$parent : ::std::rc::Weak<$parent<'a>> ,
}
#[allow(unused_variables)]
let container = ::std::rc::Rc::new(::std::cell::RefCell::new(IdsContainer::default()));
let (r, init) = rsml!{@parse_as_initialize (parse_as_initialize_end { $name container [$parent $($ids)*]}), fields: [], sub_items: [], id: [], $($rest)* };
(r, container, init)
} };
($name:ident { $($rest:tt)* }) => {
rsml!{@find_all_id (parse_as_initialize_start {$name, $($rest)*}) [] $name => $($rest)* }
};
(@parse_as_initialize_start {$name:ident, $($rest:tt)*} [$(($ids:tt $ids_ty:ident))*]) => { {
#[derive(Default)]
#[allow(non_snake_case)]
struct IdsContainer<'a> {
$($ids: ::std::rc::Weak<$ids_ty<'a>> ,)*
$name : ::std::rc::Weak<$name<'a>> ,
}
#[allow(unused_variables)]
let container = ::std::rc::Rc::new(::std::cell::RefCell::new(IdsContainer::default()));
let (r, init) = rsml!{@parse_as_initialize (parse_as_initialize_end { $name container [$name $($ids)*]}), fields: [], sub_items: [], id: [], $($rest)* };
container.borrow_mut().$name = ::std::rc::Rc::downgrade(&r);
init();
r
} };
(@parse_as_initialize ($callback:ident $callback_data:tt), fields: $fields:tt, sub_items: $sub_items:tt, id: $id:tt, ) => {
rsml!{@$callback $callback_data fields: $fields, sub_items: $sub_items, id: $id}
};
(@parse_as_initialize $callback:tt, fields: [$($fields:tt)*], sub_items: $sub_items:tt, id: $id:tt, $field:ident $(. $field_cont:ident)* : $value:expr, $($rest:tt)* ) => {
rsml!{@parse_as_initialize $callback, fields: [$($fields)* $field $(. $field_cont)* : $value , ],
sub_items: $sub_items, id: $id, $($rest)* }
};
(@parse_as_initialize $callback:tt, fields: [$($fields:tt)*], sub_items: $sub_items:tt, id: $id:tt, $field:ident $(. $field_cont:ident)* : $value:expr ) => {
rsml!{@parse_as_initialize $callback, fields: [$($fields)* $field $(. $field_cont)* : $value , ],
sub_items: $sub_items, id: $id, }
};
(@parse_as_initialize $callback:tt, fields: $fields:tt, sub_items: [$($sub_items:tt)*], id: $id:tt, $nam:ident { $($inner:tt)* } $($rest:tt)* ) => {
rsml!{@parse_as_initialize $callback, fields: $fields, sub_items: [$($sub_items)* { $nam $($inner)* } ], id: $id, $($rest)* }
};
(@parse_as_initialize $callback:tt, fields: $fields:tt, sub_items: $sub_items:tt, id: [], @id: $id:ident, $($rest:tt)* ) => {
rsml!{@parse_as_initialize $callback, fields: $fields, sub_items: $sub_items, id: [$id], $($rest)* }
};
(@parse_as_initialize $callback:tt, fields: $fields:tt, sub_items: $sub_items:tt, id: $id:tt, , $($rest:tt)* ) => {
rsml!{@parse_as_initialize $callback, fields: $fields, sub_items: $sub_items, id: $id, $($rest)* }
};
(@init_sub_items $r:ident $container:ident $ids:tt, { $name:ident $($inner:tt)* } ) => {
let (r, init) = rsml!{@parse_as_initialize (parse_as_initialize_end { $name $container $ids}), fields: [], sub_items: [], id: [], $($inner)*};
$r.add_child(r);
init
};
(@parse_as_initialize_end { $name:ident $container:ident $ids:tt } fields: [$($field:ident $(. $field_cont:ident)* : $value:expr ,)*], sub_items: [$($sub_items:tt)*], id: [$($id:tt)*]) => { {
let r = <$name>::new();
$( $container.borrow_mut().$id = ::std::rc::Rc::downgrade(&r); )*
let init = || {};
$(let i = { rsml!{ @init_sub_items r $container $ids, $sub_items} }; let init = move || { init(); i(); };)*
#[allow(unused_variables)]
let container = $container.clone();
(r.clone(), move || {init(); $(rsml!{ @init_field_with_ids r, container, $ids, $field $(. $field_cont)* , $value })* })
} };
(@find_all_id ($callback:ident $callback_data:tt) [$($ids:tt)*] $parent_type:ident => ) => {
rsml!{@$callback $callback_data [$($ids)*] }
};
(@find_all_id $callback:tt [$($ids:tt)*] $parent_type:ident => @id: $id:ident $($rest:tt)*) => {
rsml!{@find_all_id $callback [$($ids)* ($id $parent_type)] $parent_type => $($rest)* }
};
(@find_all_id $callback:tt [$($ids:tt)*] $old_parent_type:path => # $parent_type:ident $($rest:tt)*) => {
rsml!{@find_all_id $callback [$($ids)*] $parent_type => $($rest)* }
};
(@find_all_id $callback:tt [$($ids:tt)*] $old_parent_type:path => $parent_type:ident { $($ct:tt)* } $($rest:tt)*) => {
rsml!{@find_all_id $callback [$($ids)*] $parent_type => $($ct)* # $old_parent_type $($rest)* }
};
(@find_all_id $callback:tt [$($ids:tt)*] $parent_type:ident => $_x:tt $($rest:tt)*) => {
rsml!{@find_all_id $callback [$($ids)*] $parent_type => $($rest)* }
};
(@init_field_with_ids $r:ident, $container:ident, [$($id:ident)*], $field:ident $(. $field_cont:ident)* , $bind:expr) => {
{
#[allow(unused_variables)]
let container = $container.clone();
#[allow(unused_variables)]
#[allow(non_snake_case)]
$r.$field $(. $field_cont)* .set_binding(move || { $(let $id = container.borrow().$id.upgrade().unwrap();)* $bind });
}
};
(@init_field_with_ids $r:ident, $container:ident, $ids:tt, $field:ident $(. $field_cont:ident)* ,) => { };
}
#[cfg(test)]
mod tests {
rsml! {
struct Rectangle2 {
width: u32 = 2,
height: u32,
area: u32 = Rectangle2.width.value() * Rectangle2.height.value()
}
}
#[test]
fn test_rsml() {
let rec = Rectangle2::new(); rec.height.set(4);
assert_eq!(rec.area.value(), 4 * 2);
rec.height.set(8);
assert_eq!(rec.area.value(), 8 * 2);
}
#[test]
fn test_rsml_init() {
let rec = rsml! {
Rectangle2 {
height: Rectangle2.width.value() * 3,
}
};
assert_eq!(rec.area.value(), 3 * 2 * 2);
rec.width.set(8);
assert_eq!(rec.area.value(), 3 * 8 * 8);
}
}