diff --git a/mir/src/class.rs b/mir/src/class.rs index ef8ae9f..33e781e 100644 --- a/mir/src/class.rs +++ b/mir/src/class.rs @@ -15,7 +15,7 @@ pub struct Class { /// Use `class_methods` in Rust. pub class_methods: Vec>, pub static_methods: Vec>, - pub public: bool, + pub vis: Visibility, pub lifetimes: Vec, pub decorators: Vec, @@ -36,20 +36,18 @@ pub struct Field { impl Debug for Class { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let public = self.public; - write!(f, "Class {{ name: {name:?}, \ - doc: {doc:?}, \ - instance_fields: todo!, \ - static_fields: todo!, \ - constructors: todo!, \ - class_methods: todo!, \ - static_methods: todo!, \ - public: {public}, \ - lifetimes: todo!, \ - superclasses: todo! }}", - name = self.name, - doc = self.doc, - ) + f.debug_struct("Class") + .field("name", &self.name) + .field("doc", &self.doc) + .field("instance_fields", &self.instance_fields) + .field("static_fields", &self.static_fields) + .field("constructors", &self.constructors) + .field("class_methods", &self.class_methods) + .field("static_methods", &self.static_methods) + .field("vis", &self.vis) + .field("lifetimes", &self.lifetimes) + .field("superclasses", &self.superclasses) + .finish() } } @@ -64,7 +62,7 @@ impl Default for Class { constructors: vec![], class_methods: vec![], static_methods: vec![], - public: false, + vis: Visibility::Private, lifetimes: vec![], decorators: vec![], superclasses: vec![], diff --git a/mir/src/enum.rs b/mir/src/enum.rs new file mode 100644 index 0000000..6bcf59f --- /dev/null +++ b/mir/src/enum.rs @@ -0,0 +1,19 @@ +use std::marker::PhantomData; +use crate::{Doc, Function, Ident, Visibility}; + +#[derive(Debug, Default)] +pub struct Enum { + pub name: Ident, + pub doc: Option, + pub variants: Vec, + pub vis: Visibility, + /// Attributes in Rust + pub decorators: Vec, + pub methods: Vec>, +} + +#[derive(Debug)] +pub struct Variant { + pub name: Ident, + pub doc: Option, +} \ No newline at end of file diff --git a/mir/src/file.rs b/mir/src/file.rs new file mode 100644 index 0000000..b03616f --- /dev/null +++ b/mir/src/file.rs @@ -0,0 +1,29 @@ +use crate::{Class, Doc, Enum, Function, Import}; + +pub struct File { + pub imports: Vec, + pub doc: Option, + /// Code that is before function and class declarations + pub declaration: Option, + pub classes: Vec>, + pub enums: Vec>, + pub functions: Vec>, + /// Code that follows after the function and class declarations + pub code: Option, + pub package: Option, +} + +impl Default for File where T: Default { + fn default() -> Self { + Self { + doc: None, + declaration: None, + classes: vec![], + enums: vec![], + functions: vec![], + code: None, + imports: vec![], + package: None, + } + } +} diff --git a/mir/src/function.rs b/mir/src/function.rs index cb999a4..59bd569 100644 --- a/mir/src/function.rs +++ b/mir/src/function.rs @@ -1,6 +1,6 @@ use std::fmt::{Debug, Formatter}; -use crate::{Doc, Ident}; +use crate::{Doc, Ident, Visibility}; /// Localized pub enum ArgIdent { @@ -163,7 +163,7 @@ pub struct Function { pub body: T, pub doc: Option, pub async_: bool, - pub public: bool, + pub vis: Visibility, /// #[...] in Rust /// @... in Python pub annotations: Vec, @@ -175,14 +175,12 @@ impl Debug for Function T: Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "Function {{ name: {name:?}, args: debug print not impl, ret: {ret:?}, body: {body:?}, doc: {doc:?}, async_: {async_}, public: {public}, annotations: debug print not impl }}", - name = self.name, - ret = self.ret, - body = self.body, - doc = self.doc, - async_ = self.async_, - public = self.public - ) + f.debug_struct("Function") + .field("name", &self.name) + .field("ret", &self.ret) + .field("args", &"..") + .field("body", &"..") + .finish() } } @@ -198,7 +196,7 @@ impl Default for Function body: T::default(), doc: None, async_: false, - public: false, + vis: Default::default(), annotations: vec![], generic: vec![], } diff --git a/mir/src/ident.rs b/mir/src/ident.rs index c5bff72..99ad465 100644 --- a/mir/src/ident.rs +++ b/mir/src/ident.rs @@ -1,9 +1,19 @@ use std::fmt::Formatter; +use quote::TokenStreamExt; + /// Localized string #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] pub struct Ident(pub String); +impl Ident { + pub fn validate(&self) { + if self.0.contains(',') { + panic!("Ident cannot contain a comma: {}", self.0); + } + } +} + impl Ident { pub fn new(s: &'static str) -> Self { Ident(s.into()) @@ -20,4 +30,24 @@ impl PartialEq for Ident { fn eq(&self, other: &str) -> bool { self.0 == *other } -} \ No newline at end of file +} + +impl PartialEq<&str> for Ident { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + +impl quote::ToTokens for Ident { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.append(proc_macro2::Ident::new(&self.0, proc_macro2::Span::call_site())) + } +} + +impl From for proc_macro2::TokenStream { + fn from(val: Ident) -> Self { + let mut tok = proc_macro2::TokenStream::new(); + tok.append(proc_macro2::Ident::new(&val.0, proc_macro2::Span::call_site())); + tok + } +} diff --git a/mir/src/import.rs b/mir/src/import.rs index 9715f62..0afcfe0 100644 --- a/mir/src/import.rs +++ b/mir/src/import.rs @@ -13,6 +13,48 @@ pub struct Import { pub feature: Option } + +impl Import { + pub fn package(path: &str) -> Self { + Self { + path: path.to_string(), + imports: vec![], + alias: None, + vis: Visibility::Private, + feature: None, + } + } + + pub fn new(path: &str, imports: impl IntoIterator>) -> Self { + Self { + path: path.to_string(), + imports: imports + .into_iter() + .map(|s| s.into()) + .collect(), + alias: None, + vis: Visibility::Private, + feature: None, + } + } + + pub fn alias(path: &str, alias: &str) -> Self { + Self { + path: path.to_string(), + imports: Vec::new(), + alias: Some(alias.to_string()), + vis: Visibility::Private, + feature: None, + } + } + + pub fn public(mut self) -> Self { + self.vis = Visibility::Public; + self + } +} + + pub struct ImportItem { /// This might not conform to standard ident rules for the language, so its a string, not an ident. pub name: String, @@ -23,23 +65,39 @@ impl ImportItem { pub fn alias(name: &str, alias: &str) -> Self { Self { name: name.to_string(), alias: Some(alias.to_string()) } } + + pub fn validate(&self) -> Result<(), String> { + if self.name.is_empty() { + return Err("ImportItem name cannot be empty".to_string()); + } + if self.name.chars().all(|c| c.is_digit(10)) { + return Err("ImportItem name cannot be all digits".to_string()); + } + Ok(()) + } } impl From<&String> for ImportItem { fn from(s: &String) -> Self { - Self { name: s.clone(), alias: None } + let r = Self { name: s.clone(), alias: None }; + r.validate().unwrap(); + r } } impl From for ImportItem { fn from(s: String) -> Self { - Self { name: s, alias: None } + let r = Self { name: s, alias: None }; + r.validate().unwrap(); + r } } impl From<&str> for ImportItem { fn from(s: &str) -> Self { - Self { name: s.to_string(), alias: None } + let r = Self { name: s.to_string(), alias: None }; + r.validate().unwrap(); + r } } diff --git a/mir/src/lib.rs b/mir/src/lib.rs index 3d43058..f1847fa 100644 --- a/mir/src/lib.rs +++ b/mir/src/lib.rs @@ -1,13 +1,12 @@ -use core::default::Default; use core::fmt::Formatter; -use quote::TokenStreamExt; - pub use class::*; pub use doc::{Doc, DocFormat}; +pub use file::File; pub use function::{ArgIdent, build_dict, build_struct, FnArg2, Function}; pub use ident::*; pub use import::*; +pub use r#enum::*; pub use ty::*; pub use visibility::*; @@ -19,6 +18,8 @@ mod class; mod import; mod visibility; mod ident; +mod r#enum; +mod file; pub struct Interface { pub name: String, @@ -35,81 +36,12 @@ pub struct NewType { pub public: bool, } -pub struct File { - pub doc: Option, - /// Code that is before function and class declarations - pub declaration: Option, - pub classes: Vec>, - pub functions: Vec>, - /// Code that follows after the function and class declarations - pub code: Option, - pub imports: Vec, - pub package: Option, -} - pub struct Literal(pub T); pub struct Grave(String); pub struct FString(String); -impl Import { - pub fn package(path: &str) -> Self { - Self { - path: path.to_string(), - imports: vec![], - alias: None, - vis: Visibility::Private, - feature: None, - } - } - - pub fn new(path: &str, imports: impl IntoIterator>) -> Self { - Self { - path: path.to_string(), - imports: imports - .into_iter() - .map(|s| s.into()) - .collect(), - alias: None, - vis: Visibility::Private, - feature: None, - } - } - - pub fn alias(path: &str, alias: &str) -> Self { - Self { - path: path.to_string(), - imports: Vec::new(), - alias: Some(alias.to_string()), - vis: Visibility::Private, - feature: None, - } - } - - pub fn public(mut self) -> Self { - self.vis = Visibility::Public; - self - } -} - -impl Default for File - where - T: Default, -{ - fn default() -> Self { - Self { - doc: None, - declaration: None, - classes: vec![], - functions: vec![], - code: None, - imports: vec![], - package: None, - } - } -} - pub fn literal(s: impl Into) -> Literal { Literal(s.into()) } @@ -122,20 +54,6 @@ pub fn f_string(s: &str) -> Literal { Literal(FString(s.to_string())) } -impl quote::ToTokens for Ident { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - tokens.append(proc_macro2::Ident::new(&self.0, proc_macro2::Span::call_site())) - } -} - -impl From for proc_macro2::TokenStream { - fn from(val: Ident) -> Self { - let mut tok = proc_macro2::TokenStream::new(); - tok.append(proc_macro2::Ident::new(&val.0, proc_macro2::Span::call_site())); - tok - } -} - /// Specifically represents a parameter in Location::Query. We need special treatment for repeated keys. pub enum ParamKey { Key(String), diff --git a/mir_rust/src/class.rs b/mir_rust/src/class.rs index 3179459..d584747 100644 --- a/mir_rust/src/class.rs +++ b/mir_rust/src/class.rs @@ -1,9 +1,8 @@ +use mir::{Class, Field}; use proc_macro2::{Span, TokenStream}; use quote::quote; -use mir::{Class, Field}; - -use crate::{FluentBool, ToRustCode}; +use crate::ToRustCode; impl ToRustCode for Class { fn to_rust_code(self) -> TokenStream { @@ -16,7 +15,7 @@ impl ToRustCode for Class { constructors, class_methods, static_methods, - public, + vis, lifetimes, decorators, superclasses @@ -27,7 +26,7 @@ impl ToRustCode for Class { assert!(code.is_none(), "code in class body not supported in Rust"); assert!(static_methods.is_empty(), "static methods not supported in Rust"); - let vis = public.to_value(|| quote!(pub)); + let vis = vis.to_rust_code(); let fields = instance_fields.into_iter().map(|f| f.to_rust_code()); let class_methods = class_methods.into_iter().map(|m| m.to_rust_code()); diff --git a/mir_rust/src/enum.rs b/mir_rust/src/enum.rs new file mode 100644 index 0000000..66794b7 --- /dev/null +++ b/mir_rust/src/enum.rs @@ -0,0 +1,39 @@ +use proc_macro2::TokenStream; +use quote::quote; +use mir::{Enum, Variant}; +use crate::ToRustCode; + +impl ToRustCode for Enum { + fn to_rust_code(self) -> TokenStream { + let Enum { name, doc, vis, decorators, variants, methods } = self; + let vis = vis.to_rust_code(); + let doc = doc.to_rust_code(); + let variants = variants + .into_iter() + .map(|v| v.to_rust_code()); + let methods = methods + .into_iter() + .map(|m| m.to_rust_code()); + quote! { + #doc + #(#decorators)* + #vis enum #name { + #(#variants),* + } + impl #name { + #(#methods)* + } + } + } +} + +impl ToRustCode for Variant { + fn to_rust_code(self) -> TokenStream { + let Variant { name, doc } = self; + let doc = doc.to_rust_code(); + quote! { + #doc + #name + } + } +} \ No newline at end of file diff --git a/mir_rust/src/file.rs b/mir_rust/src/file.rs index 83e73c4..9ca0723 100644 --- a/mir_rust/src/file.rs +++ b/mir_rust/src/file.rs @@ -8,6 +8,7 @@ impl ToRustCode for File { let File { imports, classes, + enums, doc, functions, code, @@ -17,12 +18,14 @@ impl ToRustCode for File { let doc = doc.to_rust_code(); let functions = functions.into_iter().map(|f| f.to_rust_code()); let classes = classes.into_iter().map(|c| c.to_rust_code()); + let enums = enums.into_iter().map(|c| c.to_rust_code()); let code = code.unwrap_or_default(); quote! { #doc #(#imports)* #(#functions)* #(#classes)* + #(#enums)* #code } } diff --git a/mir_rust/src/function.rs b/mir_rust/src/function.rs index dc4473c..c7dd82b 100644 --- a/mir_rust/src/function.rs +++ b/mir_rust/src/function.rs @@ -13,15 +13,14 @@ impl ToRustCode for Function { async_, annotations, ret, - public, + vis, .. } = self; let annotations = annotations .into_iter() .map(|a| syn::parse_str::(&a).unwrap()); let doc = doc.to_rust_code(); - - let vis = public.to_value(|| quote!(pub)); + let vis = vis.to_rust_code(); let async_ = async_.to_value(|| quote!(async)); let args = args.into_iter().map(|a| a.to_rust_code()); let ret = (!ret.is_empty()).to_value(|| quote!( -> #ret)); diff --git a/mir_rust/src/lib.rs b/mir_rust/src/lib.rs index b2eb050..3c01c9a 100644 --- a/mir_rust/src/lib.rs +++ b/mir_rust/src/lib.rs @@ -9,6 +9,7 @@ mod file; mod class; mod import; mod function; +mod r#enum; /// Use this for codegen structs: Function, Class, etc. pub trait ToRustCode { @@ -164,6 +165,9 @@ fn assert_valid_ident(s: &str, original: &str) { if s.contains('.') { panic!("Dot in identifier: {}", original) } + if s.is_empty() { + panic!("Empty identifier: {}", original) + } } #[cfg(test)]