hoooooly shit parsing works, will make the code prettier tomorrow
This commit is contained in:
parent
7c2febb4ed
commit
51be5dcf55
18 changed files with 566 additions and 121 deletions
2
.tokeignore
Normal file
2
.tokeignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
syn-serde
|
||||
symbol
|
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -8,7 +8,9 @@ edition = "2018"
|
|||
members = [
|
||||
"enterprise-compiler",
|
||||
"enterprise-macros",
|
||||
|
||||
"symbol",
|
||||
"syn-serde",
|
||||
|
||||
"examples/helloworld",
|
||||
]
|
||||
|
|
|
@ -15,4 +15,6 @@ serde = "1.0.104"
|
|||
serde_derive = "1.0.104"
|
||||
spin = "0.5.2"
|
||||
symbol = { path = "../symbol" }
|
||||
syn-serde = { path = "../syn-serde" }
|
||||
syn = { version = "1.0.14", features = ["extra-traits", "full"] }
|
||||
serde_json = "1.0.48"
|
||||
|
|
|
@ -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<str>,
|
||||
datamodel: &HashMap<String, String>,
|
||||
datainit: &HashMap<String, String>,
|
||||
dom: &[Rsx],
|
||||
pub fn build(
|
||||
// name: impl AsRef<str>,
|
||||
// datamodel: &HashMap<String, String>,
|
||||
// datainit: &HashMap<String, String>,
|
||||
// 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<T>
|
||||
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<enterprise::parking_lot::Mutex<#ty>> , });
|
||||
}
|
||||
for (name, value) in datainit {
|
||||
let name = format_ident!("{}", name);
|
||||
let value = syn::parse_str::<Expr>(&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<B> {
|
||||
pub struct #name<B> {
|
||||
_b: std::marker::PhantomData<B>,
|
||||
#model
|
||||
}
|
||||
|
||||
impl<B> #name<B> {
|
||||
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<str>, code: impl AsRef<str>) {
|
||||
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<String, String> = hashmap! {
|
||||
|
|
|
@ -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<Symbol, (Type, Expr)>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub fn convert_map(map: HashMap<Symbol, (syn::Type, syn::Expr)>) -> 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<Rsx>,
|
||||
}
|
||||
|
||||
#[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<T> {
|
||||
pub tag: String,
|
||||
pub attrs: HashMap<TagLhs, String>,
|
||||
#[serde(with = "crate::tuple_map")]
|
||||
pub attrs: HashMap<TagLhs, TagRhs>,
|
||||
pub inner: Vec<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Rsx {
|
||||
Elem(Elem<Rsx>),
|
||||
Code(Box<Expr>),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
82
enterprise-compiler/src/tuple_map.rs
Normal file
82
enterprise-compiler/src/tuple_map.rs
Normal file
|
@ -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<K, V> {
|
||||
marker: PhantomData<HashMap<K, V>>,
|
||||
}
|
||||
|
||||
impl<K, V> TupleVecMapVisitor<K, V> {
|
||||
pub fn new() -> Self {
|
||||
TupleVecMapVisitor {
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, K: Eq + Hash, V> Visitor<'de> for TupleVecMapVisitor<K, V>
|
||||
where
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
type Value = HashMap<K, V>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a map")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_seq<T>(self, mut seq: T) -> Result<Self::Value, T::Error>
|
||||
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<K, V>`.
|
||||
///
|
||||
/// 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<K: Eq + Hash, V, S>(data: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<K, V>`.
|
||||
///
|
||||
/// 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<HashMap<K, V>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(TupleVecMapVisitor::new())
|
||||
}
|
|
@ -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<Id, String>,
|
||||
// model_bimap: &BiHashMap<Id, String>,
|
||||
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<DepNode, ()>;
|
|||
pub struct Visitor {
|
||||
idx: u32,
|
||||
pub(crate) deps: DependencyGraph,
|
||||
model: HashMap<Id, String>,
|
||||
model: HashMap<Id, (Type, Expr)>,
|
||||
pub(crate) impl_code: TokenStream,
|
||||
elem_attr_map: HashMap<Id, HashSet<Id>>,
|
||||
|
||||
// symbol maps
|
||||
model_bimap: BiHashMap<Id, String>,
|
||||
// model_bimap: BiHashMap<Id, String>,
|
||||
}
|
||||
|
||||
impl Visitor {
|
||||
|
@ -81,11 +81,13 @@ impl Visitor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn load_model(&mut self, model: &HashMap<String, String>) {
|
||||
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,9 +101,11 @@ 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) {
|
||||
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(*id);
|
||||
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));
|
||||
|
@ -113,6 +117,7 @@ impl Visitor {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TaggedRsx::Elem(
|
||||
node_id,
|
||||
Elem {
|
||||
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<SynError> for ParseError {
|
||||
|
@ -39,7 +47,7 @@ impl From<SynError> for ParseError {
|
|||
|
||||
enum ComponentBlock {
|
||||
Model(ModelMap),
|
||||
View(Rsx),
|
||||
View(Vec<Rsx>),
|
||||
}
|
||||
|
||||
struct Visitor(Peekable<IntoIter>);
|
||||
|
@ -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::<ModelEntry>(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<Rsx, ParseError> {
|
||||
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<Vec<Rsx>, 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<str>) -> 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<Punct>) -> Result<Punct, ParseError> {
|
||||
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<Ident, ParseError> {
|
||||
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<Group, ParseError> {
|
||||
let next_token = self.0.peek();
|
||||
if next_token.is_none() {
|
||||
|
@ -249,6 +243,47 @@ impl Visitor {
|
|||
}
|
||||
}
|
||||
|
||||
fn consume_punct(
|
||||
iter: &mut Peekable<impl Iterator<Item = TokenTree>>,
|
||||
equals: Option<char>,
|
||||
) -> Result<Punct, ParseError> {
|
||||
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<impl Iterator<Item = TokenTree>>,
|
||||
) -> Result<Ident, ParseError> {
|
||||
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<Component, ParseError>;
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
|
191
enterprise-macros/src/rsx.rs
Normal file
191
enterprise-macros/src/rsx.rs
Normal file
|
@ -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<IntoIter>);
|
||||
|
||||
impl RsxParser {
|
||||
pub fn new(tokens: impl Iterator<Item = TokenTree>) -> Self {
|
||||
let tokens = TokenStream::from_iter(tokens);
|
||||
RsxParser(tokens.into_iter().peekable())
|
||||
}
|
||||
|
||||
pub fn next_token(&mut self) -> Result<Option<RsxToken>, 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::<Lit>(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::<Expr>(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::<Lit>(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::<Expr>(group.stream())?;
|
||||
return Ok(Some(RsxToken::Code(expr)));
|
||||
}
|
||||
_ => unimplemented!("TOKEN: {:?}", token),
|
||||
};
|
||||
|
||||
unimplemented!("the fuck")
|
||||
}
|
||||
|
||||
fn consume_ident(&mut self) -> Result<Ident, ParseError> {
|
||||
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<TagLhs, TagRhs>),
|
||||
EmptyTag(Symbol, HashMap<TagLhs, TagRhs>),
|
||||
ClosingTag(Symbol),
|
||||
Str(String),
|
||||
Code(Expr),
|
||||
}
|
||||
|
||||
impl Iterator for RsxParser {
|
||||
type Item = Result<RsxToken, ParseError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.next_token() {
|
||||
Ok(Some(token)) => Some(Ok(token)),
|
||||
Ok(None) => None,
|
||||
Err(err) => Some(Err(err)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = "../.." }
|
|
@ -8,10 +8,12 @@ component! {
|
|||
}
|
||||
|
||||
view {
|
||||
<TextBox bind:value="name" />
|
||||
Hello, {name}!
|
||||
<input bind:value="name" />
|
||||
"Hello, " {name} "!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
fn main() {
|
||||
enterprise_compiler::process("helloworld", HelloWorld);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -18,3 +18,13 @@ pub trait Component<B: Backend> {
|
|||
/// 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
s.serialize_str(self.s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Deserialize<'d> for Symbol {
|
||||
fn deserialize<D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
|
||||
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<E>(self, string: &str) -> Result<Self::Value, E> {
|
||||
Ok(Symbol::from(string))
|
||||
}
|
||||
}
|
||||
|
|
1
syn-serde
Submodule
1
syn-serde
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit dff506bb8a83702e2dc82b17177dda43e6de0f3a
|
Loading…
Reference in a new issue