From 51be5dcf55bb31fad0e7ac691fab58551f194093 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 17 Feb 2020 06:07:53 -0600 Subject: [PATCH] hoooooly shit parsing works, will make the code prettier tomorrow --- .tokeignore | 2 + Cargo.lock | 29 +++- Cargo.toml | 2 + enterprise-compiler/Cargo.toml | 4 +- enterprise-compiler/src/lib.rs | 50 ++++--- enterprise-compiler/src/model.rs | 52 ++++++-- enterprise-compiler/src/tuple_map.rs | 82 ++++++++++++ enterprise-compiler/src/visitor.rs | 62 +++++---- enterprise-macros/Cargo.toml | 2 + enterprise-macros/src/lib.rs | 149 +++++++++++++-------- enterprise-macros/src/rsx.rs | 191 +++++++++++++++++++++++++++ examples/helloworld/Cargo.toml | 2 + examples/helloworld/src/build.rs | 8 +- examples/helloworld/src/main.rs | 4 +- src/lib.rs | 10 ++ symbol/Cargo.toml | 2 + symbol/src/lib.rs | 35 ++++- syn-serde | 1 + 18 files changed, 566 insertions(+), 121 deletions(-) create mode 100644 .tokeignore create mode 100644 enterprise-compiler/src/tuple_map.rs create mode 100644 enterprise-macros/src/rsx.rs create mode 160000 syn-serde diff --git a/.tokeignore b/.tokeignore new file mode 100644 index 0000000..27e6989 --- /dev/null +++ b/.tokeignore @@ -0,0 +1,2 @@ +syn-serde +symbol diff --git a/Cargo.lock b/Cargo.lock index 9df6d74..8d0d08f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,9 +67,11 @@ dependencies = [ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "symbol 0.1.0", "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn-serde 0.2.0", ] [[package]] @@ -79,8 +81,10 @@ dependencies = [ "enterprise-compiler 0.1.0", "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "symbol 0.1.0", "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn-serde 0.2.0", "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -230,6 +234,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "serde" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "serde_derive" @@ -243,7 +250,7 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.46" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -274,7 +281,7 @@ dependencies = [ "discard 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "stdweb-derive 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "stdweb-internal-macros 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "stdweb-internal-runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -303,7 +310,7 @@ dependencies = [ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -318,6 +325,8 @@ name = "symbol" version = "0.1.0" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -331,6 +340,18 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn-serde" +version = "0.2.0" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thiserror" version = "1.0.11" @@ -452,7 +473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -"checksum serde_json 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "21b01d7f0288608a01dca632cf1df859df6fd6ffa885300fc275ce2ba6221953" +"checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" diff --git a/Cargo.toml b/Cargo.toml index 21c3bd8..d1645d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,9 @@ edition = "2018" members = [ "enterprise-compiler", "enterprise-macros", + "symbol", + "syn-serde", "examples/helloworld", ] diff --git a/enterprise-compiler/Cargo.toml b/enterprise-compiler/Cargo.toml index ee138d4..7bff3dd 100644 --- a/enterprise-compiler/Cargo.toml +++ b/enterprise-compiler/Cargo.toml @@ -15,4 +15,6 @@ serde = "1.0.104" serde_derive = "1.0.104" spin = "0.5.2" symbol = { path = "../symbol" } -syn = { version = "1.0.14", features = ["extra-traits", "full"] } \ No newline at end of file +syn-serde = { path = "../syn-serde" } +syn = { version = "1.0.14", features = ["extra-traits", "full"] } +serde_json = "1.0.48" diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index b5c8f6e..363a058 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -7,26 +7,32 @@ extern crate serde_derive; pub mod model; mod visitor; +mod tuple_map; use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; -use crate::model::{Elem, Rsx, TagLhs}; +use crate::model::{Component, Elem, Rsx, TagLhs}; use crate::visitor::Visitor; use proc_macro2::TokenStream; use symbol::Symbol; use syn::Expr; -fn process( - name: impl AsRef, - datamodel: &HashMap, - datainit: &HashMap, - dom: &[Rsx], +pub fn build( + // name: impl AsRef, + // datamodel: &HashMap, + // datainit: &HashMap, + // dom: &[Rsx], + component: &Component, ) -> TokenStream { - let name = name.as_ref(); + let name = &component.name; let mut visitor = Visitor::new(); - visitor.load_model(&datamodel); - let new_dom = visitor.make_graph(&dom); + visitor.load_model(&component.model); + let new_dom = visitor.make_graph(&component.view); let toplevel_names = visitor.gen_code(&new_dom); // let graph: Graph<_, _, _> = visitor.deps.clone().into_graph(); @@ -35,17 +41,13 @@ fn process( let name = format_ident!("{}", name); let mut model = TokenStream::new(); let mut init = TokenStream::new(); - for (name, ty) in datamodel { - let name = format_ident!("{}", name); - // TODO: parse this into an actual expression tree for Vec - let ty = format_ident!("{}", ty); + for (name, (ty, value)) in component.model.iter() { + let name = format_ident!("{}", name.as_str()); + let ty: syn::Type = ty.into(); + let value: syn::Expr = value.into(); model.extend(quote! { #name : std::sync::Arc> , }); - } - for (name, value) in datainit { - let name = format_ident!("{}", name); - let value = syn::parse_str::(&value).unwrap(); init.extend( - quote! { #name : std::sync::Arc::new(enterprise::parking_lot::Mutex::new(#value)) , }, + quote! { #name : std::sync::Arc::new(enterprise::parking_lot::Mutex::new(#value .into())) , }, ); } @@ -63,13 +65,13 @@ fn process( } quote! { - struct #name { + pub struct #name { _b: std::marker::PhantomData, #model } impl #name { - fn new(_: &B) -> Self { + pub fn new(_: &B) -> Self { #name { _b: std::marker::PhantomData::default(), #init @@ -87,6 +89,14 @@ fn process( } } +pub fn process(mod_name: impl AsRef, code: impl AsRef) { + let component: Component = serde_json::from_str(code.as_ref()).unwrap(); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let mut out_file = File::create(out_dir.join(format!("{}.rs", mod_name.as_ref()))).unwrap(); + let tokens = build(&component); + write!(out_file, "{}", tokens); +} + // #[proc_macro] // pub fn example(_input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { // let helloworld_datamodel: HashMap = hashmap! { diff --git a/enterprise-compiler/src/model.rs b/enterprise-compiler/src/model.rs index fc2b2fc..8cbb6e5 100644 --- a/enterprise-compiler/src/model.rs +++ b/enterprise-compiler/src/model.rs @@ -1,39 +1,71 @@ use std::collections::HashMap; +use proc_macro2::TokenStream; use symbol::Symbol; -use syn::{Expr, Type}; +use syn_serde::{Expr, Syn, Type}; pub type Id = Symbol; pub type ModelMap = HashMap; -#[derive(Debug)] +pub fn convert_map(map: HashMap) -> ModelMap { + map.into_iter() + .map(|(name, (ty, expr))| { + let ty = ty.to_adapter(); + let expr = expr.to_adapter(); + (name, (ty, expr)) + }) + .collect() +} + +#[derive(Debug, Serialize, Deserialize)] pub struct Component { pub name: String, + #[serde(with = "crate::tuple_map")] pub model: ModelMap, - pub view: Rsx, + pub view: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum TagLhs { Bind(String), - // On(String), - // Plain(String), + Plain(String), + On(String), + #[doc(hidden)] _Nonexhaustive, } -#[derive(Debug, Default)] +#[derive(Debug, Serialize, Deserialize)] +pub enum TagRhs { + Code(Expr), + Text(String), +} + +impl Clone for TagRhs { + fn clone(&self) -> Self { + match self { + TagRhs::Code(expr) => { + let expr: syn::Expr = Syn::from_adapter(&*expr); + TagRhs::Code(expr.clone().to_adapter()) + } + TagRhs::Text(string) => TagRhs::Text(string.clone()), + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] pub struct Elem { pub tag: String, - pub attrs: HashMap, + #[serde(with = "crate::tuple_map")] + pub attrs: HashMap, pub inner: Vec, } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub enum Rsx { Elem(Elem), - Code(Box), + Code(Expr), Text(String), #[doc(hidden)] @@ -54,7 +86,7 @@ impl TaggedRsx { pub fn get_id(&self) -> Id { match self { TaggedRsx::Elem(id, _) | TaggedRsx::Code(id, _) | TaggedRsx::Text(id, _) => *id, - _ => unimplemented!(), + _ => unimplemented!("tagged rsx"), } } } diff --git a/enterprise-compiler/src/tuple_map.rs b/enterprise-compiler/src/tuple_map.rs new file mode 100644 index 0000000..f401831 --- /dev/null +++ b/enterprise-compiler/src/tuple_map.rs @@ -0,0 +1,82 @@ +// https://github.com/daboross/serde-tuple-vec-map/blob/master/src/lib.rs + +use std::hash::Hash; +use std::marker::PhantomData; +use std::fmt; +use std::cmp; +use std::collections::HashMap; + +use serde::{de::{Visitor, Deserialize, Deserializer, SeqAccess}, ser::{Serialize, Serializer}}; + +struct TupleVecMapVisitor { + marker: PhantomData>, +} + +impl TupleVecMapVisitor { + pub fn new() -> Self { + TupleVecMapVisitor { + marker: PhantomData, + } + } +} + +impl<'de, K: Eq + Hash, V> Visitor<'de> for TupleVecMapVisitor +where + K: Deserialize<'de>, + V: Deserialize<'de>, +{ + type Value = HashMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + #[inline] + fn visit_unit(self) -> Result { + Ok(HashMap::new()) + } + + #[inline] + fn visit_seq(self, mut seq: T) -> Result + where + T: SeqAccess<'de>, + { + let mut values = HashMap::new(); + + while let Some((key, value)) = seq.next_element()? { + values.insert(key, value); + } + + Ok(values) + } +} + +/// Serialize an array of `(K, V)` pairs as if it were a `HashMap`. +/// +/// In formats where dictionaries are ordered, this maintains the input data's order. Each pair is treated as a single +/// entry into the dictionary. +/// +/// Behavior when duplicate keys are present in the data is unspecified and serializer-dependent. This function does +/// not check for duplicate keys and will not warn the serializer. +pub fn serialize(data: &HashMap, serializer: S) -> Result +where + S: Serializer, + K: Serialize, + V: Serialize, +{ + serializer.collect_seq(data.iter().map(|x| (x.0, x.1))) +} + +/// Deserialize to a `Vec<(K, V)>` as if it were a `HashMap`. +/// +/// This directly deserializes into the returned vec with no intermediate allocation. +/// +/// In formats where dictionaries are ordered, this maintains the input data's order. +pub fn deserialize<'de, K: Eq + Hash, V, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + K: Deserialize<'de>, + V: Deserialize<'de>, +{ + deserializer.deserialize_seq(TupleVecMapVisitor::new()) +} diff --git a/enterprise-compiler/src/visitor.rs b/enterprise-compiler/src/visitor.rs index ed8af62..eb9887c 100644 --- a/enterprise-compiler/src/visitor.rs +++ b/enterprise-compiler/src/visitor.rs @@ -6,9 +6,10 @@ use petgraph::graphmap::DiGraphMap; use petgraph::visit::Dfs; use proc_macro2::{TokenStream, TokenTree}; use quote::ToTokens; -use syn::Expr; +use syn::{Expr, Type}; +use syn_serde::Syn; -use crate::model::{Elem, Id, Rsx, TagLhs, TaggedRsx}; +use crate::model::{Elem, Id, ModelMap, Rsx, TagLhs, TagRhs, TaggedRsx}; use crate::Symbol; #[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] @@ -26,13 +27,13 @@ pub enum DepNode { impl DepNode { fn gen_update_code( &self, - model_bimap: &BiHashMap, + // model_bimap: &BiHashMap, updates: &mut TokenStream, update_func: &mut TokenStream, ) { match self { DepNode::ModelValue(sym) => { - let sym_name = format_ident!("{}", model_bimap.get_by_left(&sym).unwrap()); + let sym_name = format_ident!("{}", sym.to_string()); let inner_lock = format_ident!("inner_lock_{}", Symbol::gensym().as_str()); updates.extend(quote! { let #inner_lock = self.#sym_name.clone(); @@ -66,12 +67,11 @@ type DependencyGraph = DiGraphMap; pub struct Visitor { idx: u32, pub(crate) deps: DependencyGraph, - model: HashMap, + model: HashMap, pub(crate) impl_code: TokenStream, elem_attr_map: HashMap>, - // symbol maps - model_bimap: BiHashMap, + // model_bimap: BiHashMap, } impl Visitor { @@ -81,11 +81,13 @@ impl Visitor { } } - pub fn load_model(&mut self, model: &HashMap) { - for (key, value) in model { + pub fn load_model(&mut self, model: &ModelMap) { + for (key, (ty, init)) in model { let id = Symbol::gensym(); - self.model_bimap.insert(id, key.clone()); - self.model.insert(id, value.clone()); + // self.model_bimap.insert(id, key.clone()); + let ty = Syn::from_adapter(&*ty); + let init = Syn::from_adapter(&*init); + self.model.insert(key.clone(), (ty, init)); } // self.model.extend(model.clone()); } @@ -99,16 +101,19 @@ impl Visitor { let tag_inner = self.make_graph(&inner); for (lhs, rhs) in attrs { if let TagLhs::Bind(attr) = lhs { - if let Some(id) = self.model_bimap.get_by_right(rhs) { - let from = DepNode::RsxAttr(node_id, Symbol::from(attr)); - let to = DepNode::ModelValue(*id); - self.deps.add_edge(from, to, ()); - if let Some(set) = self.elem_attr_map.get_mut(&node_id) { - set.insert(Symbol::from(attr)); - } else { - let mut set = HashSet::new(); - set.insert(Symbol::from(attr)); - self.elem_attr_map.insert(node_id, set); + if let TagRhs::Text(text) = rhs { + let text_sym = Symbol::from(text); + if self.model.contains_key(&text_sym) { + let from = DepNode::RsxAttr(node_id, Symbol::from(attr)); + let to = DepNode::ModelValue(text_sym); + self.deps.add_edge(from, to, ()); + if let Some(set) = self.elem_attr_map.get_mut(&node_id) { + set.insert(Symbol::from(attr)); + } else { + let mut set = HashSet::new(); + set.insert(Symbol::from(attr)); + self.elem_attr_map.insert(node_id, set); + } } } } @@ -123,14 +128,15 @@ impl Visitor { ) } Rsx::Code(expr) => { - let deps = self.extract_model_dependencies(expr); + let syn_expr = Syn::from_adapter(&*expr); + let deps = self.extract_model_dependencies(&syn_expr); for dep in deps { let from = DepNode::ModelValue(dep); let to = DepNode::RsxSpan(node_id); self.deps.add_edge(from, to, ()); } - TaggedRsx::Code(node_id, expr.clone()) + TaggedRsx::Code(node_id, Box::new(syn_expr.clone().to_adapter())) } Rsx::Text(literal) => TaggedRsx::Text(node_id, literal.clone()), _ => unimplemented!(), @@ -156,7 +162,7 @@ impl Visitor { while let Some(nx) = dfs.next(&self.deps) { if nx != starting { nx.gen_update_code( - &self.model_bimap, + // &self.model_bimap, &mut updates, &mut update_func, ); @@ -204,7 +210,7 @@ impl Visitor { } }); } - _ => unimplemented!(), + _ => unimplemented!("gen_code tagged rsx"), } names.push(format!("{}", make_node_id)); } @@ -218,8 +224,10 @@ impl Visitor { for token in tokens.into_iter() { if let TokenTree::Ident(ident) = token { - if let Some(id) = self.model_bimap.get_by_right(&ident.to_string()) { - result.insert(*id); + // if let Some(id) = self.model_bimap.get_by_right(&ident.to_string()) { + let sym = Symbol::from(ident.to_string()); + if self.model.contains_key(&sym) { + result.insert(sym); } // result.insert(format!("{}", ident)); } diff --git a/enterprise-macros/Cargo.toml b/enterprise-macros/Cargo.toml index 70cf499..48bf765 100644 --- a/enterprise-macros/Cargo.toml +++ b/enterprise-macros/Cargo.toml @@ -13,4 +13,6 @@ quote = "1.0.2" thiserror = "1.0.9" symbol = { path = "../symbol" } enterprise-compiler = { path = "../enterprise-compiler" } +syn-serde = { path = "../syn-serde" } syn = { version = "1.0.14", features = ["extra-traits", "full"] } +serde_json = "1.0.48" diff --git a/enterprise-macros/src/lib.rs b/enterprise-macros/src/lib.rs index adca18b..b0b2dfd 100644 --- a/enterprise-macros/src/lib.rs +++ b/enterprise-macros/src/lib.rs @@ -2,20 +2,26 @@ extern crate proc_macro; #[macro_use] extern crate quote; +mod rsx; + use std::collections::HashMap; use std::iter::FromIterator; use std::iter::Peekable; -use enterprise_compiler::model::{Component, ModelMap, Rsx}; +use quote::ToTokens; +use enterprise_compiler::model::{Component, Elem, ModelMap, Rsx}; +use syn_serde::Syn; use proc_macro2::{ token_stream::IntoIter, Delimiter, Group, Ident, Punct, Spacing, TokenStream, TokenTree, }; use symbol::Symbol; use syn::{ parse::{Parse, ParseStream}, - Error as SynError, Expr, Result as SynResult, Token, Type, + Error as SynError, Expr, Lit, Result as SynResult, Token, Type, }; +use crate::rsx::{RsxParser, RsxToken}; + #[derive(Debug)] enum ParseError { ExpectedKeyword(Symbol, Ident), @@ -23,12 +29,14 @@ enum ParseError { ExpectedGroup(TokenTree), ExpectedPunct(TokenTree), WrongDelimiter(Delimiter, Delimiter), - WrongPunct(Punct, Punct), + WrongPunct(char, Punct), Syn(SynError), UnexpectedEOF, UnexpectedKeyword, MissingModel, MissingView, + InvalidRsx(TokenTree), + UnmatchedOpenTag(TokenTree), } impl From for ParseError { @@ -39,7 +47,7 @@ impl From for ParseError { enum ComponentBlock { Model(ModelMap), - View(Rsx), + View(Vec), } struct Visitor(Peekable); @@ -55,7 +63,7 @@ impl Visitor { } self.consume_keyword("component")?; - let name = self.consume_ident()?.to_string(); + let name = consume_ident(&mut self.0)?.to_string(); let def = self.consume_group(Delimiter::Brace)?; let mut def_visitor = Visitor::from_tokens(def.stream()); let mut model_map = None; @@ -84,7 +92,7 @@ impl Visitor { return Ok(None); } - let next_ident = self.consume_ident()?; + let next_ident = consume_ident(&mut self.0)?; match next_ident.to_string().as_ref() { "model" => { let next_group = self.consume_group(Delimiter::Brace)?; @@ -156,7 +164,7 @@ impl Visitor { let stream = TokenStream::from_iter(buf); let item = syn::parse2::(stream)?; - println!("ITEM: {:?}", item); + // println!("ITEM: {:?}", item); Ok(Some(( Symbol::from(item.name.to_string()), @@ -173,18 +181,40 @@ impl Visitor { break; } } - Ok(map) + Ok(enterprise_compiler::model::convert_map(map)) } - fn consume_view(&mut self) -> Result { - let lt = self.consume_punct(Some(Punct::new('<', Spacing::Alone)))?; - let gt = self.consume_punct(Some(Punct::new('>', Spacing::Alone)))?; - Ok(Rsx::Text(String::new())) + fn consume_view(&mut self) -> Result, ParseError> { + let mut rsx_parser = RsxParser::new(self.0.clone()); + let mut result = Vec::new(); + + while let Some(next_token) = rsx_parser.next() { + match next_token? { + RsxToken::EmptyTag(name, attrs) => { + let elem = Elem { + tag: name.to_string(), + attrs, + inner: vec![], + }; + let el = Rsx::Elem(elem); + result.push(el); + } + RsxToken::Code(expr) => { + result.push(Rsx::Code(expr.to_adapter())); + } + RsxToken::Str(string) => { + result.push(Rsx::Text(string)); + } + _ => (), + } + } + + Ok(result) } fn consume_keyword(&mut self, keyword: impl AsRef) -> Result<(), ParseError> { let keyword = keyword.as_ref(); - let ident = self.consume_ident()?; + let ident = consume_ident(&mut self.0)?; let ident_str = ident.to_string(); if keyword == &ident_str { @@ -194,42 +224,6 @@ impl Visitor { } } - fn consume_punct(&mut self, equals: Option) -> Result { - let next_token = self.0.peek(); - if next_token.is_none() { - return Err(ParseError::UnexpectedEOF); - } - - let next_token = self.0.next().expect("unreachable"); - if let TokenTree::Punct(punct) = next_token { - if let Some(equals) = equals { - if punct.as_char() == equals.as_char() && punct.spacing() == equals.spacing() { - Ok(punct) - } else { - Err(ParseError::WrongPunct(equals, punct)) - } - } else { - Ok(punct) - } - } else { - Err(ParseError::ExpectedPunct(next_token)) - } - } - - fn consume_ident(&mut self) -> Result { - let next_token = self.0.peek(); - if next_token.is_none() { - return Err(ParseError::UnexpectedEOF); - } - - let next_token = self.0.next().expect("unreachable"); - if let TokenTree::Ident(ident) = next_token { - Ok(ident) - } else { - Err(ParseError::ExpectedIdent(next_token)) - } - } - fn consume_group(&mut self, delimiter: Delimiter) -> Result { let next_token = self.0.peek(); if next_token.is_none() { @@ -249,6 +243,47 @@ impl Visitor { } } +fn consume_punct( + iter: &mut Peekable>, + equals: Option, +) -> Result { + let next_token = iter.peek(); + if next_token.is_none() { + return Err(ParseError::UnexpectedEOF); + } + + let next_token = iter.next().expect("unreachable"); + if let TokenTree::Punct(punct) = next_token { + if let Some(equals) = equals { + if punct.as_char() == equals { + Ok(punct) + } else { + Err(ParseError::WrongPunct(equals, punct)) + } + } else { + Ok(punct) + } + } else { + Err(ParseError::ExpectedPunct(next_token)) + } +} + +fn consume_ident( + iter: &mut Peekable>, +) -> Result { + let next_token = iter.peek(); + if next_token.is_none() { + return Err(ParseError::UnexpectedEOF); + } + + let next_token = iter.next().expect("unreachable"); + if let TokenTree::Ident(ident) = next_token { + Ok(ident) + } else { + Err(ParseError::ExpectedIdent(next_token)) + } +} + impl Iterator for Visitor { type Item = Result; @@ -264,11 +299,19 @@ impl Iterator for Visitor { #[proc_macro] pub fn component(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { let input_tokens: TokenStream = input_tokens.into(); - println!("TOKENS: {:?}", input_tokens); + // println!("TOKENS: {:?}", input_tokens); let visitor = Visitor::from_tokens(input_tokens); + + // TODO: allow importing and stuff + let mut output = TokenStream::new(); for component in visitor { - println!("- {:?}", component); + println!("- {:#?}", component); + let component = component.expect("holy shiet"); + let name = format_ident!("{}", component.name); + let serialized = serde_json::to_string(&component).expect("fucking json"); + output.extend(quote! { + const #name: &'static str = #serialized; + }); } - panic!(); - (quote! {}).into() + output.into() } diff --git a/enterprise-macros/src/rsx.rs b/enterprise-macros/src/rsx.rs new file mode 100644 index 0000000..e3932f6 --- /dev/null +++ b/enterprise-macros/src/rsx.rs @@ -0,0 +1,191 @@ +use std::collections::HashMap; +use std::iter::FromIterator; +use std::iter::Peekable; + +use enterprise_compiler::model::{TagLhs, TagRhs}; +use proc_macro2::{token_stream::IntoIter, Delimiter, Ident, Spacing, TokenStream, TokenTree}; +use symbol::Symbol; +use syn::{Expr, Lit}; +use syn_serde::Syn; + +use crate::ParseError; +use crate::{consume_ident, consume_punct}; + +pub(crate) struct RsxParser(Peekable); + +impl RsxParser { + pub fn new(tokens: impl Iterator) -> Self { + let tokens = TokenStream::from_iter(tokens); + RsxParser(tokens.into_iter().peekable()) + } + + pub fn next_token(&mut self) -> Result, ParseError> { + let token = self.0.peek(); + if token.is_none() { + return Ok(None); + } + + let token = self.0.next().expect("unreachable"); + match token { + TokenTree::Punct(ref punct) if punct.as_char() == '<' => { + let next_token = self.0.peek(); + if next_token.is_none() { + return Err(ParseError::UnmatchedOpenTag(token)); + } + + let next_token = next_token.expect("unreachable"); + let is_closing = if let TokenTree::Punct(punct2) = next_token { + if punct2.as_char() == '/' { + self.0.next(); + true + } else { + false + } + } else { + false + }; + + let name = self.consume_ident()?; + if is_closing { + return Ok(Some(RsxToken::ClosingTag(Symbol::from(name.to_string())))); + } + + // read until closing tag + let mut buf = Vec::new(); + let mut prev_tag = None; + let mut is_empty = false; + loop { + let next_token = self.0.peek(); + if next_token.is_none() { + // probably wrong error? + return Err(ParseError::UnexpectedEOF); + } + + let next_token = self.0.next().expect("unreachable"); + if let TokenTree::Punct(ref punct) = next_token { + if punct.as_char() == '>' { + if let Some(TokenTree::Punct(ref punct2)) = prev_tag { + if punct2.as_char() == '/' { + buf.truncate(buf.len() - 1); + is_empty = true; + } + } + break; + } + } + prev_tag = Some(next_token.clone()); + buf.push(next_token); + } + + let mut attrs = HashMap::new(); + let mut iter = buf.into_iter().peekable(); + loop { + // consume a single attr + let next_token = iter.peek(); + if next_token.is_none() { + break; + } + + let name_or_prefix = consume_ident(&mut iter)?.to_string(); + let lhs = if let Some(TokenTree::Punct(ref punct)) = iter.peek() { + if punct.as_char() == ':' { + iter.next(); + let name = consume_ident(&mut iter)?.to_string(); + if name_or_prefix == "bind" { + TagLhs::Bind(name) + } else if name_or_prefix == "on" { + TagLhs::On(name) + } else { + unimplemented!("these are wrong states") + } + } else if punct.as_char() == '=' { + TagLhs::Plain(name_or_prefix.to_string()) + } else { + unimplemented!("these are wrong states") + } + } else { + unimplemented!("these are wrong states") + }; + consume_punct(&mut iter, Some('=')); + + let next_token = iter.next(); + let rhs = match next_token { + Some(TokenTree::Literal(lit)) => { + let mut stream = TokenStream::from(TokenTree::Literal(lit)); + let lit = syn::parse2::(stream)?; + if let Lit::Str(string) = lit { + TagRhs::Text(string.value()) + } else { + unimplemented!("grrr") + } + } + Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => { + let expr = syn::parse2::(group.stream())?; + TagRhs::Code(expr.to_adapter()) + } + _ => unimplemented!("wrong state: {:?}", next_token), + }; + + attrs.insert(lhs, rhs); + } + + let variant = if is_empty { + RsxToken::EmptyTag + } else { + RsxToken::OpeningTag + }; + return Ok(Some(variant(Symbol::from(name.to_string()), attrs))); + } + TokenTree::Literal(lit) => { + let mut stream = TokenStream::from(TokenTree::Literal(lit)); + let lit = syn::parse2::(stream)?; + + if let Lit::Str(string) = lit { + return Ok(Some(RsxToken::Str(string.value()))); + } + } + TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => { + let expr = syn::parse2::(group.stream())?; + return Ok(Some(RsxToken::Code(expr))); + } + _ => unimplemented!("TOKEN: {:?}", token), + }; + + unimplemented!("the fuck") + } + + fn consume_ident(&mut self) -> Result { + let next_token = self.0.peek(); + if next_token.is_none() { + return Err(ParseError::UnexpectedEOF); + } + + let next_token = self.0.next().expect("unreachable"); + if let TokenTree::Ident(ident) = next_token { + Ok(ident) + } else { + Err(ParseError::ExpectedIdent(next_token)) + } + } +} + +#[derive(Debug)] +pub(crate) enum RsxToken { + OpeningTag(Symbol, HashMap), + EmptyTag(Symbol, HashMap), + ClosingTag(Symbol), + Str(String), + Code(Expr), +} + +impl Iterator for RsxParser { + type Item = Result; + + fn next(&mut self) -> Option { + match self.next_token() { + Ok(Some(token)) => Some(Ok(token)), + Ok(None) => None, + Err(err) => Some(Err(err)), + } + } +} diff --git a/examples/helloworld/Cargo.toml b/examples/helloworld/Cargo.toml index c3e2525..35c410b 100644 --- a/examples/helloworld/Cargo.toml +++ b/examples/helloworld/Cargo.toml @@ -8,7 +8,9 @@ build = "src/build.rs" [build-dependencies] enterprise-compiler = { path = "../../enterprise-compiler" } enterprise-macros = { path = "../../enterprise-macros" } +enterprise = { path = "../.." } [dependencies] stdweb = "0.4.20" +enterprise-macros = { path = "../../enterprise-macros" } enterprise = { path = "../.." } \ No newline at end of file diff --git a/examples/helloworld/src/build.rs b/examples/helloworld/src/build.rs index b4699ef..9248d35 100644 --- a/examples/helloworld/src/build.rs +++ b/examples/helloworld/src/build.rs @@ -8,10 +8,12 @@ component! { } view { - - Hello, {name}! + + "Hello, " {name} "!" } } } -fn main() {} +fn main() { + enterprise_compiler::process("helloworld", HelloWorld); +} diff --git a/examples/helloworld/src/main.rs b/examples/helloworld/src/main.rs index c42e258..eb8d722 100644 --- a/examples/helloworld/src/main.rs +++ b/examples/helloworld/src/main.rs @@ -1,13 +1,13 @@ #[macro_use] extern crate enterprise; -extern crate stdweb; +enterprise_mod!(helloworld); use std::sync::Arc; use enterprise::{Backend, Component, Web}; -example!(); +use crate::helloworld::HelloWorld; fn main() { stdweb::initialize(); diff --git a/src/lib.rs b/src/lib.rs index ea24e84..ff93fda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,13 @@ pub trait Component { /// TODO: replace this with a real init function. fn create(&self, el: &crate::stdweb::web::Element); } + +/// Declares a mod +#[macro_export] +macro_rules! enterprise_mod { + ($vis:vis $name:ident) => { + $vis mod $name { + include!(concat!(env!("OUT_DIR"), "/", stringify!($name), ".rs")); + } + } +} diff --git a/symbol/Cargo.toml b/symbol/Cargo.toml index 399ea7b..a4759e4 100644 --- a/symbol/Cargo.toml +++ b/symbol/Cargo.toml @@ -9,3 +9,5 @@ edition = "2018" [dependencies] lazy_static = "1.4.0" spin = "0.5.2" +serde_derive = "1.0.104" +serde = "1.0.104" diff --git a/symbol/src/lib.rs b/symbol/src/lib.rs index c9d1b35..925c5ba 100644 --- a/symbol/src/lib.rs +++ b/symbol/src/lib.rs @@ -1,13 +1,20 @@ // cribbed from https://github.com/remexre/symbol-rs +#[macro_use] +extern crate serde_derive; + use std::cmp::Ordering; use std::collections::BTreeSet; -use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use std::fmt::{self, Debug, Display, Formatter, Result as FmtResult}; use std::mem::{forget, transmute}; use std::ops::Deref; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use lazy_static::lazy_static; +use serde::{ + de::{Deserialize, Deserializer, Visitor}, + ser::{Serialize, Serializer}, +}; use spin::Mutex; lazy_static! { @@ -111,3 +118,29 @@ fn leak_string(s: String) -> &'static str { forget(s); out } + +// SERDE + +impl Serialize for Symbol { + fn serialize(&self, s: S) -> Result { + s.serialize_str(self.s) + } +} + +impl<'d> Deserialize<'d> for Symbol { + fn deserialize>(d: D) -> Result { + d.deserialize_str(SymVisitor) + } +} + +struct SymVisitor; + +impl<'d> Visitor<'d> for SymVisitor { + type Value = Symbol; + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "symbol") + } + fn visit_str(self, string: &str) -> Result { + Ok(Symbol::from(string)) + } +} diff --git a/syn-serde b/syn-serde new file mode 160000 index 0000000..dff506b --- /dev/null +++ b/syn-serde @@ -0,0 +1 @@ +Subproject commit dff506bb8a83702e2dc82b17177dda43e6de0f3a