fix code according to proptest

This commit is contained in:
Michael Zhang 2020-02-21 02:37:03 -06:00
parent f9c478276d
commit 96df945dbc
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
9 changed files with 158 additions and 52 deletions

View file

@ -1,7 +1,6 @@
mod props; mod props;
use std::collections::HashMap; use std::collections::BTreeMap;
use std::hash::{BuildHasher, Hash};
use std::iter::{self, FromIterator}; use std::iter::{self, FromIterator};
use proc_macro2::{Span, TokenStream, TokenTree}; use proc_macro2::{Span, TokenStream, TokenTree};
@ -13,11 +12,9 @@ pub use self::props::*;
pub type Id = Symbol; pub type Id = Symbol;
pub type ModelMap = HashMap<Symbol, (Type, Expr)>; pub type ModelMap = BTreeMap<Symbol, (Type, Expr)>;
pub fn convert_map<T: Hash + Eq, S: BuildHasher>( pub fn convert_map<T: Ord>(map: BTreeMap<T, (syn::Type, syn::Expr)>) -> BTreeMap<T, (Type, Expr)> {
map: HashMap<T, (syn::Type, syn::Expr), S>,
) -> HashMap<T, (Type, Expr)> {
map.into_iter() map.into_iter()
.map(|(left, (ty, expr))| { .map(|(left, (ty, expr))| {
let ty = ty.to_adapter(); let ty = ty.to_adapter();
@ -37,6 +34,7 @@ pub struct Component {
impl ToTokens for Component { impl ToTokens for Component {
fn to_tokens(&self, stream: &mut TokenStream) { fn to_tokens(&self, stream: &mut TokenStream) {
let name = format_ident!("{}", self.name);
let model = TokenStream::from_iter(self.model.iter().map(|(name, (ty, init))| { let model = TokenStream::from_iter(self.model.iter().map(|(name, (ty, init))| {
let name = format_ident!("{}", name.as_str()); let name = format_ident!("{}", name.as_str());
let ty = syn::Type::from_adapter(ty); let ty = syn::Type::from_adapter(ty);
@ -45,7 +43,7 @@ impl ToTokens for Component {
})); }));
let view = TokenStream::from_iter(self.view.iter().map(|rsx| rsx.to_token_stream())); let view = TokenStream::from_iter(self.view.iter().map(|rsx| rsx.to_token_stream()));
stream.extend(quote! { stream.extend(quote! {
component { component #name {
model { #model } model { #model }
view { #view } view { #view }
} }
@ -53,7 +51,7 @@ impl ToTokens for Component {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq)]
pub enum TagLhs { pub enum TagLhs {
Bind(String), Bind(String),
Plain(String), Plain(String),
@ -89,6 +87,20 @@ pub enum TagRhs {
Text(String), Text(String),
} }
impl PartialEq<TagRhs> for TagRhs {
fn eq(&self, other: &TagRhs) -> bool {
match (self, other) {
(TagRhs::Code(expr), TagRhs::Code(other)) => {
syn::Expr::from_adapter(expr) == syn::Expr::from_adapter(other)
}
(TagRhs::Text(string), TagRhs::Text(other)) => string == other,
_ => false,
}
}
}
impl Eq for TagRhs {}
impl Clone for TagRhs { impl Clone for TagRhs {
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self { match self {
@ -116,11 +128,11 @@ impl ToTokens for TagRhs {
} }
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Elem<T> { pub struct Elem<T> {
pub tag: String, pub tag: String,
#[serde(with = "crate::tuple_map")] #[serde(with = "crate::tuple_map")]
pub attrs: HashMap<TagLhs, TagRhs>, pub attrs: BTreeMap<TagLhs, TagRhs>,
pub inner: Option<Vec<T>>, pub inner: Option<Vec<T>>,
} }
@ -153,6 +165,21 @@ pub enum Rsx {
_Nonexhaustive, _Nonexhaustive,
} }
impl PartialEq<Rsx> for Rsx {
fn eq(&self, other: &Rsx) -> bool {
match (self, other) {
(Rsx::Elem(this), Rsx::Elem(other)) => this == other,
(Rsx::Code(expr), Rsx::Code(other)) => {
syn::Expr::from_adapter(expr) == syn::Expr::from_adapter(other)
}
(Rsx::Text(this), Rsx::Text(other)) => this == other,
_ => false,
}
}
}
impl Eq for Rsx {}
impl ToTokens for Rsx { impl ToTokens for Rsx {
fn to_tokens(&self, stream: &mut TokenStream) { fn to_tokens(&self, stream: &mut TokenStream) {
match self { match self {
@ -161,7 +188,7 @@ impl ToTokens for Rsx {
} }
Rsx::Code(expr) => { Rsx::Code(expr) => {
let expr = syn::Expr::from_adapter(expr); let expr = syn::Expr::from_adapter(expr);
stream.extend(quote! { #expr }); stream.extend(quote! { { #expr } });
} }
Rsx::Text(string) => { Rsx::Text(string) => {
let string = syn::Lit::Str(syn::LitStr::new(string.as_ref(), Span::call_site())); let string = syn::Lit::Str(syn::LitStr::new(string.as_ref(), Span::call_site()));

View file

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::BTreeMap;
use proptest::{ use proptest::{
collection::{hash_map, vec, SizeRange}, collection::{btree_map, vec, SizeRange},
option::{self, Probability}, option::{self, Probability},
prelude::*, prelude::*,
string::string_regex, string::string_regex,
@ -15,7 +15,7 @@ use super::{Component, Elem, Rsx};
prop_compose! { prop_compose! {
pub fn arbitrary_component() ( pub fn arbitrary_component() (
name in ident_strategy(), name in ident_strategy(),
model in hash_map( model in btree_map(
ident_strategy().prop_map(|ident| Symbol::from(ident)), ident_strategy().prop_map(|ident| Symbol::from(ident)),
// TODO: maybe actually have tests for syn? // TODO: maybe actually have tests for syn?
(Just(syn::parse_str::<Type>("()").unwrap().to_adapter()), Just(syn::parse_str::<Expr>("()").unwrap().to_adapter())), (Just(syn::parse_str::<Type>("()").unwrap().to_adapter()), Just(syn::parse_str::<Expr>("()").unwrap().to_adapter())),
@ -35,9 +35,9 @@ pub fn arbitrary_view() -> impl Strategy<Value = Rsx> {
Just(Rsx::Code( Just(Rsx::Code(
syn::parse_str::<Expr>("()").unwrap().to_adapter() syn::parse_str::<Expr>("()").unwrap().to_adapter()
)), )),
any::<String>().prop_map(Rsx::Text), string_regex(r"[:print:]+").unwrap().prop_map(Rsx::Text),
]; ];
leaf.prop_recursive(4, 16, 5, |inner| { leaf.prop_recursive(2, 4, 5, |inner| {
prop_oneof![( prop_oneof![(
ident_strategy(), ident_strategy(),
option::weighted(Probability::new(0.9), vec(inner, SizeRange::default())), option::weighted(Probability::new(0.9), vec(inner, SizeRange::default())),
@ -45,7 +45,7 @@ pub fn arbitrary_view() -> impl Strategy<Value = Rsx> {
.prop_map(|(tag, inner)| Rsx::Elem(Elem { .prop_map(|(tag, inner)| Rsx::Elem(Elem {
tag, tag,
// TODO: ouais // TODO: ouais
attrs: HashMap::new(), attrs: BTreeMap::new(),
inner, inner,
}))] }))]
}) })

View file

@ -1,18 +1,20 @@
// https://github.com/daboross/serde-tuple-vec-map/blob/master/src/lib.rs // https://github.com/daboross/serde-tuple-vec-map/blob/master/src/lib.rs
use std::fmt; use std::fmt;
use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::collections::HashMap; use std::collections::BTreeMap;
use serde::{ use serde::{
de::{Deserialize, Deserializer, SeqAccess, Visitor}, de::{Deserialize, Deserializer, SeqAccess, Visitor},
ser::{Serialize, Serializer}, ser::{Serialize, Serializer},
}; };
trait Delegate: Ord {}
struct TupleVecMapVisitor<K, V> { struct TupleVecMapVisitor<K, V> {
marker: PhantomData<HashMap<K, V>>, marker: PhantomData<BTreeMap<K, V>>,
} }
impl<K, V> TupleVecMapVisitor<K, V> { impl<K, V> TupleVecMapVisitor<K, V> {
@ -23,12 +25,12 @@ impl<K, V> TupleVecMapVisitor<K, V> {
} }
} }
impl<'de, K: Eq + Hash, V> Visitor<'de> for TupleVecMapVisitor<K, V> impl<'de, K: Ord, V> Visitor<'de> for TupleVecMapVisitor<K, V>
where where
K: Deserialize<'de>, K: Deserialize<'de>,
V: Deserialize<'de>, V: Deserialize<'de>,
{ {
type Value = HashMap<K, V>; type Value = BTreeMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map") formatter.write_str("a map")
@ -36,7 +38,7 @@ where
#[inline] #[inline]
fn visit_unit<E>(self) -> Result<Self::Value, E> { fn visit_unit<E>(self) -> Result<Self::Value, E> {
Ok(HashMap::new()) Ok(BTreeMap::new())
} }
#[inline] #[inline]
@ -44,7 +46,7 @@ where
where where
T: SeqAccess<'de>, T: SeqAccess<'de>,
{ {
let mut values = HashMap::new(); let mut values = BTreeMap::new();
while let Some((key, value)) = seq.next_element()? { while let Some((key, value)) = seq.next_element()? {
values.insert(key, value); values.insert(key, value);
@ -54,14 +56,14 @@ where
} }
} }
/// Serialize an array of `(K, V)` pairs as if it were a `HashMap<K, V>`. /// Serialize an array of `(K, V)` pairs as if it were a `BTreeMap<K, V>`.
/// ///
/// In formats where dictionaries are ordered, this maintains the input data's order. Each pair is treated as a single /// In formats where dictionaries are ordered, this maintains the input data's order. Each pair is treated as a single
/// entry into the dictionary. /// entry into the dictionary.
/// ///
/// Behavior when duplicate keys are present in the data is unspecified and serializer-dependent. This function does /// 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. /// 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> pub fn serialize<K: Ord, V, S>(data: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
K: Serialize, K: Serialize,
@ -70,12 +72,12 @@ where
serializer.collect_seq(data.iter().map(|x| (x.0, x.1))) 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>`. /// Deserialize to a `Vec<(K, V)>` as if it were a `BTreeMap<K, V>`.
/// ///
/// This directly deserializes into the returned vec with no intermediate allocation. /// This directly deserializes into the returned vec with no intermediate allocation.
/// ///
/// In formats where dictionaries are ordered, this maintains the input data's order. /// 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> pub fn deserialize<'de, K: Ord, V, D>(deserializer: D) -> Result<BTreeMap<K, V>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
K: Deserialize<'de>, K: Deserialize<'de>,

View file

@ -4,4 +4,4 @@
# #
# It is recommended to check this file in to source control so that # It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases. # everyone who runs the test benefits from these saved cases.
cc 2a51d2b2a9a6442d273f2835de2bc13bc778cab5d1333e190cc6fb90c5d5e50a # shrinks to tree = Component { name: "A", model: {}, view: [] } cc c87725a641776defa0a321d11950acba1b25c60d510345f7a487df38d08795bc # shrinks to tree = Component { name: "a", model: {}, view: [] }

View file

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::BTreeMap;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::iter::Peekable; use std::iter::Peekable;
@ -9,7 +9,7 @@ use proc_macro2::{
use symbol::Symbol; use symbol::Symbol;
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
Error as SynError, Expr, Result as SynResult, Token, Type, Error as SynError, Expr, Lit, Result as SynResult, Token, Type,
}; };
use syn_serde::Syn; use syn_serde::Syn;
@ -26,8 +26,12 @@ pub(crate) enum ParseError {
Syn(SynError), Syn(SynError),
UnexpectedEOF, UnexpectedEOF,
UnexpectedKeyword, UnexpectedKeyword,
UnexpectedToken(TokenTree),
MissingModel, MissingModel,
MissingView, MissingView,
ClosedTooFar,
WrongClosingTag(String, String),
UnrecognizedLiteral(Lit),
// InvalidRsx(TokenTree), // InvalidRsx(TokenTree),
UnmatchedOpenTag(TokenTree), UnmatchedOpenTag(TokenTree),
} }
@ -148,6 +152,7 @@ impl Visitor {
} }
buf.push(next_token); buf.push(next_token);
} }
println!("model buf: {:?}", buf);
// probably shouldn't happen? // probably shouldn't happen?
if buf.len() == 0 { if buf.len() == 0 {
@ -166,10 +171,11 @@ impl Visitor {
))) )))
}; };
let mut map = HashMap::new(); let mut map = BTreeMap::new();
while let Some((name, ty, init, comma)) = single_def()? { while let Some((name, ty, init, comma)) = single_def()? {
println!("single_def => ({}, {:?}, {:?}, {})", name, ty, init, comma);
map.insert(name, (ty, init)); map.insert(name, (ty, init));
if comma { if !comma {
break; break;
} }
} }
@ -179,18 +185,42 @@ impl Visitor {
fn consume_view(&mut self) -> Result<Vec<Rsx>, ParseError> { fn consume_view(&mut self) -> Result<Vec<Rsx>, ParseError> {
let mut rsx_parser = RsxParser::new(self.0.clone()); let mut rsx_parser = RsxParser::new(self.0.clone());
let mut result = Vec::new(); let mut result = Vec::new();
let mut tag_stack = Vec::new();
while let Some(next_token) = rsx_parser.next() { while let Some(next_token) = rsx_parser.next() {
match next_token? { match next_token? {
RsxToken::EmptyTag(name, attrs) => { RsxToken::EmptyTag(tag, attrs) => {
let elem = Elem { let elem = Elem {
tag: name.to_string(), tag: tag.to_string(),
attrs, attrs,
inner: None, inner: None,
}; };
let el = Rsx::Elem(elem); let el = Rsx::Elem(elem);
result.push(el); result.push(el);
} }
RsxToken::OpeningTag(tag, attrs) => {
tag_stack.push((tag, attrs, result.clone()));
result.clear();
}
RsxToken::ClosingTag(tag) => {
if let Some((last_tag, attrs, mut last_result)) = tag_stack.pop() {
if tag.as_str() == last_tag.as_str() {
last_result.push(Rsx::Elem(Elem {
tag: tag.to_string(),
attrs: attrs.clone(),
inner: Some(result),
}));
result = last_result;
} else {
return Err(ParseError::WrongClosingTag(
last_tag.to_string(),
tag.to_string(),
));
}
} else {
return Err(ParseError::ClosedTooFar);
}
}
RsxToken::Code(expr) => { RsxToken::Code(expr) => {
result.push(Rsx::Code(expr.to_adapter())); result.push(Rsx::Code(expr.to_adapter()));
} }
@ -290,20 +320,38 @@ impl Iterator for Visitor {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::BTreeMap;
use enterprise_compiler::model::*; use enterprise_compiler::model::*;
use proptest::prelude::*; use proptest::prelude::*;
use quote::ToTokens; use quote::ToTokens;
use syn_serde::Syn;
use super::Visitor; use super::Visitor;
fn convert<K: Clone + Ord>(
map: &BTreeMap<K, (syn_serde::Type, syn_serde::Expr)>,
) -> BTreeMap<K, (syn::Type, syn::Expr)> {
map.iter()
.map(|(name, (ty, expr))| {
let ty = syn::Type::from_adapter(ty);
let expr = syn::Expr::from_adapter(expr);
(name.clone(), (ty, expr))
})
.collect()
}
proptest! { proptest! {
#[test] #[test]
fn tokens_parse_compatibility(tree in arbitrary_component()) { fn tokens_parse_compatibility(tree in arbitrary_component()) {
let tokens = tree.to_token_stream(); let tokens = tree.to_token_stream();
let mut visitor = Visitor::from_tokens(tokens.clone()); let mut visitor = Visitor::from_tokens(tokens.clone());
let tree2 = visitor.next().unwrap().unwrap(); let tree2 = visitor.next().unwrap().unwrap();
prop_assert_eq!(format!("{}", tokens), format!("{}", tree2.to_token_stream())); // compare the trees
prop_assert_eq!(tree.name, tree2.name, "name");
prop_assert_eq!(convert(&tree.model), convert(&tree2.model), "model");
prop_assert_eq!(tree.view, tree2.view, "view");
} }
} }
} }

View file

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::BTreeMap;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::iter::Peekable; use std::iter::Peekable;
@ -8,7 +8,7 @@ use symbol::Symbol;
use syn::{Expr, Lit}; use syn::{Expr, Lit};
use syn_serde::Syn; use syn_serde::Syn;
use crate::parser::{ParseError, consume_ident, consume_punct}; use crate::parser::{consume_ident, consume_punct, ParseError};
pub(crate) struct RsxParser(Peekable<IntoIter>); pub(crate) struct RsxParser(Peekable<IntoIter>);
@ -46,6 +46,8 @@ impl RsxParser {
let name = self.consume_ident()?; let name = self.consume_ident()?;
if is_closing { if is_closing {
// TODO: assert next is >
self.0.next();
return Ok(Some(RsxToken::ClosingTag(Symbol::from(name.to_string())))); return Ok(Some(RsxToken::ClosingTag(Symbol::from(name.to_string()))));
} }
@ -76,7 +78,7 @@ impl RsxParser {
buf.push(next_token); buf.push(next_token);
} }
let mut attrs = HashMap::new(); let mut attrs = BTreeMap::new();
let mut iter = buf.into_iter().peekable(); let mut iter = buf.into_iter().peekable();
loop { loop {
// consume a single attr // consume a single attr
@ -133,24 +135,50 @@ impl RsxParser {
} else { } else {
RsxToken::OpeningTag RsxToken::OpeningTag
}; };
return Ok(Some(variant(Symbol::from(name.to_string()), attrs))); Ok(Some(variant(Symbol::from(name.to_string()), attrs)))
} }
TokenTree::Literal(lit) => { TokenTree::Literal(lit) => {
let stream = TokenStream::from(TokenTree::Literal(lit)); let stream = TokenStream::from(TokenTree::Literal(lit));
let lit = syn::parse2::<Lit>(stream)?; let lit = syn::parse2::<Lit>(stream)?;
if let Lit::Str(string) = lit { if let Lit::Str(string) = lit {
return Ok(Some(RsxToken::Str(string.value()))); Ok(Some(RsxToken::Str(string.value())))
} else {
Err(ParseError::UnrecognizedLiteral(lit))
} }
} }
TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => { TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => {
let expr = syn::parse2::<Expr>(group.stream())?; let mut stream = group.stream().into_iter();
return Ok(Some(RsxToken::Code(expr)));
}
_ => unimplemented!("TOKEN: {:?}", token),
};
unimplemented!("the fuck") enum GroupType {
Pound,
Slash,
None,
}
// based on svelte for now, we'll probably change up the syntax a bit later
let is_special = match stream.next() {
Some(TokenTree::Punct(punct)) if punct.as_char() == '#' => GroupType::Pound,
Some(TokenTree::Punct(punct)) if punct.as_char() == '/' => GroupType::Slash,
_ => GroupType::None,
};
if let GroupType::None = is_special {
let expr = syn::parse2::<Expr>(group.stream())?;
Ok(Some(RsxToken::Code(expr)))
} else {
match stream.next() {
Some(TokenTree::Ident(ident)) if ident == "for" => {
// syntax: {#for pattern in expr} {/for}
Ok(Some(RsxToken::StartFor()))
}
Some(other) => Err(ParseError::ExpectedIdent(other)),
None => Err(ParseError::UnexpectedEOF),
}
}
}
token => Err(ParseError::UnexpectedToken(token)),
}
} }
fn consume_ident(&mut self) -> Result<Ident, ParseError> { fn consume_ident(&mut self) -> Result<Ident, ParseError> {
@ -170,11 +198,12 @@ impl RsxParser {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum RsxToken { pub(crate) enum RsxToken {
OpeningTag(Symbol, HashMap<TagLhs, TagRhs>), OpeningTag(Symbol, BTreeMap<TagLhs, TagRhs>),
EmptyTag(Symbol, HashMap<TagLhs, TagRhs>), EmptyTag(Symbol, BTreeMap<TagLhs, TagRhs>),
ClosingTag(Symbol), ClosingTag(Symbol),
Str(String), Str(String),
Code(Expr), Code(Expr),
StartFor(),
} }
impl Iterator for RsxParser { impl Iterator for RsxParser {

View file

@ -10,7 +10,7 @@ component! {
view { view {
<input on:submit={|evt| { todos.push(evt.name); }} /> <input on:submit={|evt| { todos.push(evt.name); }} />
<ul> <ul>
{#for (key, todo) in todos} {#for (key, todo) in todos }
<li>{todo} <a on:click={|_| { todos.remove(key); }}>[x]</a></li> <li>{todo} <a on:click={|_| { todos.remove(key); }}>[x]</a></li>
{/for} {/for}
</ul> </ul>

0
output.txt Normal file
View file

View file

@ -22,7 +22,7 @@ proc-macro2 = { version = "1.0", default-features = false }
serde = { version = "1.0.99", features = ["derive"] } serde = { version = "1.0.99", features = ["derive"] }
serde_derive = "1.0.99" # This is necessary to make `-Z minimal-versions` build successful. serde_derive = "1.0.99" # This is necessary to make `-Z minimal-versions` build successful.
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
syn = { version = "1.0.5", default-features = false, features = ["full"] } syn = { version = "1.0.5", default-features = false, features = ["extra-traits", "full"] }
[dev-dependencies] [dev-dependencies]
quote = "1.0" quote = "1.0"