WIP rusalka compiler

This commit is contained in:
Daniel Bulant 2023-10-28 02:35:58 +02:00
parent 55476f971f
commit 38473da15f
15 changed files with 633 additions and 73 deletions

16
Cargo.lock generated
View file

@ -641,6 +641,8 @@ name = "mangades"
version = "0.1.0"
dependencies = [
"mangui",
"rusalka",
"rusalka-macro",
]
[[package]]
@ -935,6 +937,20 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "rusalka"
version = "0.1.0"
dependencies = [
"mangui",
]
[[package]]
name = "rusalka-macro"
version = "0.1.0"
dependencies = [
"quote",
]
[[package]]
name = "rustix"
version = "0.38.20"

View file

@ -3,5 +3,7 @@ package.authors = ["Daniel Bulant"]
resolver = "2"
members = [
"ui",
"mangades"
"mangades",
"rusalka",
"rusalka-macro"
]

View file

@ -6,4 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mangui = { path = "../ui"}
mangui = { path = "../ui"}
rusalka = { path = "../rusalka"}
rusalka-macro = { path = "../rusalka-macro"}

View file

@ -0,0 +1,32 @@
use std::sync::{Arc, Mutex};
use mangui::SharedNode;
use rusalka::{component::Component, nodes::primitives::{Rectangle, RectangleAttributes}, SharedComponent};
pub struct DemoComponent {
rect: SharedComponent<Rectangle>,
attrs: DemoComponentAttributes,
}
pub struct DemoComponentAttributes {}
impl Component for DemoComponent {
type ComponentAttrs = DemoComponentAttributes;
fn new(attrs: Self::ComponentAttrs) -> Self {
Self {
rect: Arc::new(Mutex::new(Rectangle::new(RectangleAttributes {}))),
attrs,
}
}
fn set(&mut self, attrs: Self::ComponentAttrs) { self.attrs = attrs; }
fn get(&self) -> &Self::ComponentAttrs { &self.attrs }
fn mount(&self, parent: &SharedNode, before: Option<&SharedNode>) {
self.rect.lock().unwrap().mount(parent, before);
}
fn unmount(&self) {
self.rect.lock().unwrap().unmount();
}
fn update(&self) {}
}

View file

@ -0,0 +1,29 @@
use rusalka_macro::make_component;
use std::default::Default;
use mangui::{SharedNode, nodes::Style, taffy::prelude::Size, femtovg::{Paint, Color}};
use rusalka::nodes::primitives::{Rectangle, RectangleAttributes};
make_component!(
ComponentDemo,
Logic {
let test = false;
}
Component {
@Rectangle {
// style: Style {
// layout: TaffyStyle {
// min_size: Size {
// width: Dimension::Points(50.),
// height: Dimension::Points(100.)
// },
// ..Default::default()
// },
// ..Default::default()
// },
// fill: Paint::color(Color::rgb(0, 0, 255)),
// radius: 5.,
// ..Default::default()
}
}
);

View file

@ -2,76 +2,15 @@ use std::sync::{RwLock, Arc, mpsc};
use mangui::{self, nodes::{layout::Layout, self, Style, TaffyStyle}, taffy::{self, prelude::Size, style::Dimension}, femtovg::{Paint, Color}, SharedNode, MainEntry};
mod component_demo;
mod component_demo_syntax;
fn main() {
let (tx, rx) = mpsc::channel();
let tx = Arc::new(tx);
let _tx = Arc::new(tx);
let mut root = Layout::default();
root.style.layout.display = taffy::style::Display::Flex;
root.style.layout.flex_direction = taffy::style::FlexDirection::Row;
root.children.push(Arc::new(RwLock::new(nodes::primitives::Rectangle {
style: Style {
overflow: nodes::Overflow::Visible,
layout: TaffyStyle {
min_size: Size {
width: Dimension::Points(100.),
height: Dimension::Points(100.)
},
..Default::default()
}
},
fill: Paint::color(Color::rgb(255, 0, 0)),
radius: 10.,
events: Default::default()
})));
root.children.push(Arc::new(RwLock::new(Layout {
style: Style {
overflow: nodes::Overflow::Visible,
layout: TaffyStyle {
min_size: Size {
width: Dimension::Points(100.),
height: Dimension::Points(100.)
},
flex_grow: 1.,
display: taffy::style::Display::Flex,
flex_direction: taffy::style::FlexDirection::Column,
..Default::default()
}
},
children: vec![
Arc::new(RwLock::new(nodes::primitives::Rectangle {
style: Style {
overflow: nodes::Overflow::Visible,
layout: TaffyStyle {
min_size: Size {
width: Dimension::Points(50.),
height: Dimension::Points(50.)
},
flex_grow: 1.,
..Default::default()
}
},
fill: Paint::color(Color::rgb(0, 255, 0)),
radius: 5.,
events: Default::default()
})),
Arc::new(RwLock::new(nodes::primitives::Rectangle {
style: Style {
overflow: nodes::Overflow::Visible,
layout: TaffyStyle {
min_size: Size {
width: Dimension::Points(50.),
height: Dimension::Points(50.)
},
..Default::default()
}
},
fill: Paint::color(Color::rgb(0, 255, 255)),
radius: 5.,
events: Default::default()
}))
],
events: Default::default()
})));
let right_node = Arc::new(RwLock::new(nodes::primitives::Rectangle {
style: Style {
overflow: nodes::Overflow::Visible,
@ -81,11 +20,13 @@ fn main() {
height: Dimension::Points(100.)
},
..Default::default()
}
},
cursor: Default::default()
},
fill: Paint::color(Color::rgb(0, 0, 255)),
radius: 0.,
events: Default::default()
events: Default::default(),
parent: None
}));
root.children.push(right_node.clone());
right_node.clone().write().unwrap().events.add_handler(Box::new(move |event| {

13
rusalka-macro/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "rusalka-macro"
version = "0.1.0"
edition = "2021"
authors.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
quote = "1.0"
[lib]
proc-macro = true

410
rusalka-macro/src/lib.rs Normal file
View file

@ -0,0 +1,410 @@
use proc_macro::{TokenStream, TokenTree, Ident, Group, Punct, Span};
use quote::quote;
struct Attribute {
name: Ident,
default: Option<TokenStream>,
type_: TokenStream
}
#[derive(Debug)]
struct ComponentUsed {
name: Ident,
contents: TokenStream,
parent: Option<usize>
}
#[proc_macro]
pub fn make_component(item: TokenStream) -> TokenStream {
dbg!(&item);
let mut last_identifier = None;
let mut item = item.into_iter();
let name = item.next().unwrap();
item.next().unwrap();
let str_name = name.to_string();
dbg!(&name);
// let mut attributes: Vec<Attribute> = Vec::new();
// let mut struct_values = Vec::new();
let mut main_logic = Vec::new();
// let mut reactive_variables = Vec::new();
let mut components_used: Vec<ComponentUsed> = Vec::new();
for token in item {
match token {
TokenTree::Ident(ident) => {
last_identifier = Some(ident.to_string());
let ident = ident.to_string();
if ident == "Logic" {
// dbg!("Logic");
} else if ident == "Component" {
// dbg!("Component");
} else {
panic!("Unknown identifier: {:?}", ident);
}
},
TokenTree::Group(group) => {
match &last_identifier {
Some(ident) => {
match ident.as_str() {
"Logic" => {
main_logic.push(group.stream());
},
"Component" => {
// Example syntax:
// @Layout {
// style: Style { ... },
// @Rectangle {
// fill: Paint::color(Color::rgb(0, 0, 255)),
// }
// @Rectangle {
// fill: Paint::color(Color::rgb(0, 0, 255)),
// }
// }
// non-component properties cannot contain components
// top level must contain only components
// components can contain components
let mut stream = group.stream().into_iter();
while let Some(token) = stream.next() {
match token {
TokenTree::Punct(punct) => {
if punct.as_char() != '@' {
panic!("Expected @");
}
},
_ => panic!("Expected @")
};
let ident = stream.next().unwrap();
let ident = match ident {
TokenTree::Ident(ident) => ident,
_ => panic!("Expected ident after @")
};
let group = stream.next().unwrap();
let group = match group {
TokenTree::Group(group) => group,
_ => panic!("Expected group after ident")
};
let components = parse_components(ident, group, components_used.len(), None);
components_used.extend(components);
}
},
_ => unreachable!()
}
},
None => {
panic!("Unexpected group: {:?}", group);
}
}
},
_ => {
panic!("Unknown token: {:?}", token);
}
}
}
dbg!(&components_used);
let mut output = TokenStream::new();
// Component struct
output.extend(TokenStream::from(quote!(pub struct)));
output.extend(Some(name.clone()));
let mut component_struct_stream = TokenStream::new();
let mut i = 0;
for component in &components_used {
let ident = Ident::new(&format!("comp{}", i), Span::call_site());
let midstream = TokenStream::from(quote!(: rusalka::SharedComponent<));
let component_name = component.name.clone();
component_struct_stream.extend(Some(TokenTree::Ident(ident)));
component_struct_stream.extend(midstream);
component_struct_stream.extend(Some(TokenTree::Ident(component_name)));
component_struct_stream.extend(TokenStream::from(quote!(>,)));
i+=1;
}
// Attributes struct
let attributes_ident = Ident::new(&format!("{str_name}Attributes"), Span::call_site());
component_struct_stream.extend(TokenStream::from(quote!(attrs: )));
component_struct_stream.extend(Some(TokenTree::Ident(attributes_ident.clone())));
let component_struct_group = Group::new(proc_macro::Delimiter::Brace, component_struct_stream);
output.extend(Some(TokenTree::Group(component_struct_group)));
let attributes_struct_stream = TokenStream::new();
// attributes TBD
output.extend(TokenStream::from(quote!(pub struct)));
output.extend(Some(TokenTree::Ident(attributes_ident.clone())));
output.extend(Some(TokenTree::Group(Group::new(proc_macro::Delimiter::Brace, attributes_struct_stream))));
// Component impl
output.extend(TokenStream::from(quote!(impl rusalka::component::Component for)));
output.extend(Some(name.clone()));
let mut component_impl_stream = TokenStream::new();
component_impl_stream.extend(TokenStream::from(quote!(type ComponentAttrs =)));
component_impl_stream.extend(Some(TokenTree::Ident(attributes_ident.clone())));
component_impl_stream.extend(TokenStream::from(quote!(;)));
// fn new
component_impl_stream.extend(TokenStream::from(quote!(fn new(attrs: Self::ComponentAttrs) -> Self)));
let mut new_stream = TokenStream::new();
new_stream.extend(main_logic);
new_stream.extend(TokenStream::from(quote!(Self)));
let mut new_returnvalue_stream = TokenStream::new();
i = 0;
for component in &components_used {
let ident = Ident::new(&format!("comp{}", i), Span::call_site());
let component_name = component.name.clone();
dbg!(&component_name);
new_returnvalue_stream.extend(Some(TokenTree::Ident(ident)));
new_returnvalue_stream.extend(TokenStream::from(quote!(:)));
let mut component_stream = TokenStream::new();
component_stream.extend(Some(TokenTree::Ident(component_name.clone())));
component_stream.extend(TokenStream::from(quote!(::new)));
let mut component_new_stream = TokenStream::new();
let component_attributes = Ident::new(&format!("{}Attributes", component_name.to_string()), Span::call_site());
component_new_stream.extend(Some(TokenTree::Ident(component_attributes)));
let component_attributes_group_stream = TokenStream::new();
let components_attributes_group = Group::new(proc_macro::Delimiter::Brace, component_attributes_group_stream);
component_new_stream.extend(Some(TokenTree::Group(components_attributes_group)));
let component_new_group = Group::new(proc_macro::Delimiter::Parenthesis, component_new_stream);
component_stream.extend(Some(TokenTree::Group(component_new_group)));
new_returnvalue_stream.extend(wrap_in_arcmutex(component_stream));
new_returnvalue_stream.extend(TokenStream::from(quote!(,)));
i+=1;
}
new_returnvalue_stream.extend(TokenStream::from(quote!(attrs)));
let new_returnvalue_group = Group::new(proc_macro::Delimiter::Brace, new_returnvalue_stream);
new_stream.extend(Some(TokenTree::Group(new_returnvalue_group)));
let new_group = Group::new(proc_macro::Delimiter::Brace, new_stream);
component_impl_stream.extend(Some(TokenTree::Group(new_group)));
// fn set
component_impl_stream.extend(TokenStream::from(quote!(fn set(&mut self, attrs: Self::ComponentAttrs) { self.attrs = attrs; })));
// fn get
component_impl_stream.extend(TokenStream::from(quote!(fn get(&self) -> &Self::ComponentAttrs { &self.attrs })));
// fn mount
component_impl_stream.extend(TokenStream::from(quote!(fn mount(&self, parent: &mangui::SharedNode, before: Option<&mangui::SharedNode>))));
let mut mount_stream = TokenStream::new();
for i in 0..components_used.len() {
let ident = Ident::new(&format!("comp{}", i), Span::call_site());
let mut component_stream = TokenStream::new();
component_stream.extend(TokenStream::from(quote!(self.)));
component_stream.extend(Some(TokenTree::Ident(ident)));
component_stream.extend(TokenStream::from(quote!(.lock().unwrap().mount)));
let mut component_mount_stream = TokenStream::new();
component_mount_stream.extend(TokenStream::from(quote!(parent)));
component_mount_stream.extend(TokenStream::from(quote!(,)));
component_mount_stream.extend(TokenStream::from(quote!(before)));
let component_mount_group = Group::new(proc_macro::Delimiter::Parenthesis, component_mount_stream);
component_stream.extend(Some(TokenTree::Group(component_mount_group)));
mount_stream.extend(component_stream);
mount_stream.extend(TokenStream::from(quote!(;)));
}
let mount_group = Group::new(proc_macro::Delimiter::Brace, mount_stream);
component_impl_stream.extend(Some(TokenTree::Group(mount_group)));
// fn unmount
component_impl_stream.extend(TokenStream::from(quote!(fn unmount(&self))));
let mut unmount_stream = TokenStream::new();
for i in 0..components_used.len() {
let ident = Ident::new(&format!("comp{}", i), Span::call_site());
let mut component_stream = TokenStream::new();
component_stream.extend(TokenStream::from(quote!(self.)));
component_stream.extend(Some(TokenTree::Ident(ident)));
component_stream.extend(TokenStream::from(quote!(.lock().unwrap().unmount())));
unmount_stream.extend(component_stream);
unmount_stream.extend(TokenStream::from(quote!(;)));
}
let unmount_group = Group::new(proc_macro::Delimiter::Brace, unmount_stream);
component_impl_stream.extend(Some(TokenTree::Group(unmount_group)));
// fn update
component_impl_stream.extend(TokenStream::from(quote!(fn update(&self) {})));
output.extend(Some(TokenTree::Group(Group::new(proc_macro::Delimiter::Brace, component_impl_stream))));
dbg!(&output);
println!("{}", output.to_string());
println!();
output
}
fn wrap_in_arcmutex(stream: TokenStream) -> TokenStream {
let mut output = TokenStream::new();
output.extend(TokenStream::from(quote!(std::sync::Arc::new)));
let mut mutex_group = Group::new(proc_macro::Delimiter::Parenthesis, stream);
let mut mutex_stream = TokenStream::new();
mutex_stream.extend(TokenStream::from(quote!(std::sync::Mutex::new)));
mutex_stream.extend(Some(TokenTree::Group(mutex_group)));
let arc_group = Group::new(proc_macro::Delimiter::Parenthesis, mutex_stream);
output.extend(Some(TokenTree::Group(arc_group)));
output
}
/// Call this after @
/// Will return the main component as well as any sub-components
/// name: the name of the component
/// group: the group of tokens that make up the component
/// next: the index of this component in the components_used vector
/// parent: the index of the parent component in the components_used vector
fn parse_components(name: Ident, group: Group, next: usize, parent: Option<usize>) -> Vec<ComponentUsed> {
let mut components_found = Vec::new();
let mut group = group.stream().into_iter();
let mut self_stream = TokenStream::new();
// dbg!(&name);
let this_component = ComponentUsed {
name,
contents: self_stream.clone(),
parent
};
components_found.push(this_component);
while let Some(token) = group.next() {
match token {
TokenTree::Punct(punct) => {
if punct.as_char() == '@' {
let ident = group.next().unwrap();
let ident = match ident {
TokenTree::Ident(ident) => ident,
_ => panic!("Expected ident after @")
};
let group = group.next().unwrap();
let group = match group {
TokenTree::Group(group) => group,
_ => panic!("Expected group after ident")
};
let components = parse_components(ident, group, next + components_found.len() + 1, Some(next));
components_found.extend(components);
} else {
while let Some(token) = group.next() {
match token {
TokenTree::Punct(punct) => {
if punct.as_char() == ',' {
break;
} else {
self_stream.extend(Some(TokenTree::Punct(punct)));
}
},
_ => {
self_stream.extend(Some(token));
}
}
}
}
},
_ => {
// skip until next ',', writing to self_stream
while let Some(token) = group.next() {
match token {
TokenTree::Punct(punct) => {
if punct.as_char() == ',' {
break;
} else {
self_stream.extend(Some(TokenTree::Punct(punct)));
}
},
_ => {
self_stream.extend(Some(token));
}
}
}
}
}
}
components_found.get_mut(0).unwrap().contents = self_stream;
components_found
}

10
rusalka/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "rusalka"
version = "0.1.0"
edition = "2021"
authors.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mangui = { path = "../ui"}

12
rusalka/src/component.rs Normal file
View file

@ -0,0 +1,12 @@
use mangui::SharedNode;
/// A rusalka component
pub trait Component {
type ComponentAttrs;
fn new(attr: Self::ComponentAttrs) -> Self;
fn get(&self) -> &Self::ComponentAttrs;
fn set(&mut self, attr: Self::ComponentAttrs);
fn mount(&self, parent: &SharedNode, before: Option<&SharedNode>);
fn update(&self);
fn unmount(&self);
}

8
rusalka/src/lib.rs Normal file
View file

@ -0,0 +1,8 @@
use std::sync::{Arc, Mutex};
use component::Component;
pub mod component;
pub mod nodes;
pub type SharedComponent<T: Component> = Arc<Mutex<T>>;

3
rusalka/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

29
rusalka/src/nodes/mod.rs Normal file
View file

@ -0,0 +1,29 @@
use std::sync::Arc;
use mangui::SharedNode;
pub mod primitives;
pub fn detach(node: &SharedNode) {
if let Some(parent) = node.read().unwrap().parent() {
parent.write().unwrap().remove_child(node).unwrap();
}
node.clone().write().unwrap().set_parent(None);
}
pub fn insert(parent: &SharedNode, node: &SharedNode, before: Option<&SharedNode>) {
match before {
Some(before) => {
parent.write().unwrap().add_child_before(node.clone(), before).unwrap();
node.write().unwrap().set_parent(Some(Arc::downgrade(parent)));
},
None => {
append(parent, node);
}
}
}
pub fn append(parent: &SharedNode, node: &SharedNode) {
parent.write().unwrap().add_child(node.clone()).unwrap();
node.write().unwrap().set_parent(Some(Arc::downgrade(parent)));
}

View file

@ -0,0 +1,53 @@
// DemoComponent
use std::sync::{Arc, RwLock};
use mangui::{SharedNode, nodes::{primitives, Style}, taffy::prelude::Size, femtovg::{Paint, Color}};
use crate::component::Component;
use super::{insert, detach};
pub struct Rectangle {
node: SharedNode,
attrs: RectangleAttributes
}
#[derive(Default)]
pub struct RectangleAttributes {}
impl Component for Rectangle {
type ComponentAttrs = RectangleAttributes;
fn new(attrs: Self::ComponentAttrs) -> Self {
Self {
node: Arc::new(RwLock::new(primitives::Rectangle {
style: Style {
layout: mangui::nodes::TaffyStyle {
min_size: Size {
width: mangui::taffy::style::Dimension::Points(50.),
height: mangui::taffy::style::Dimension::Points(100.)
},
..Default::default()
},
..Default::default()
},
fill: Paint::color(Color::rgb(0, 0, 255)),
radius: 5.,
..Default::default()
})),
attrs
}
}
fn set(&mut self, attrs: Self::ComponentAttrs) { self.attrs = attrs; }
fn get(&self) -> &Self::ComponentAttrs { &self.attrs }
fn mount(&self, parent: &SharedNode, before: Option<&SharedNode>) {
insert(parent, &self.node, before);
}
fn update(&self) {}
fn unmount(&self) {
detach(&self.node);
}
}

View file

@ -100,7 +100,7 @@ pub fn run_event_loop(entry: MainEntry) -> () {
event_loop.run(move |event, target| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseWheel { device_id, delta, phase, .. } => {},
WindowEvent::MouseWheel { device_id: _, delta: _, phase: _, .. } => {},
WindowEvent::CursorMoved { device_id, position, .. } => {
let mouse_value = mouse_values.get(&device_id);
let (movement, location, mouse_value) = match mouse_value {
@ -152,8 +152,8 @@ pub fn run_event_loop(entry: MainEntry) -> () {
window.request_redraw();
}
},
WindowEvent::DroppedFile(path) => {},
WindowEvent::HoveredFile(path) => {},
WindowEvent::DroppedFile(_path) => {},
WindowEvent::HoveredFile(_path) => {},
WindowEvent::HoveredFileCancelled => {},
WindowEvent::Focused(focused) => {
match &focus_path {
@ -184,7 +184,7 @@ pub fn run_event_loop(entry: MainEntry) -> () {
};
},
WindowEvent::ModifiersChanged(new_modifiers) => { modifiers = new_modifiers; },
WindowEvent::KeyboardInput { device_id, event, is_synthetic } => {},
WindowEvent::KeyboardInput { device_id: _, event: _, is_synthetic: _ } => {},
WindowEvent::MouseInput { device_id, state, button, .. } => {
let mouse_value = mouse_values.get(&device_id);
let mut mouse_value = match mouse_value {