diff options
Diffstat (limited to 'ebml_derive/src/lib.rs')
-rw-r--r-- | ebml_derive/src/lib.rs | 177 |
1 files changed, 132 insertions, 45 deletions
diff --git a/ebml_derive/src/lib.rs b/ebml_derive/src/lib.rs index 2c9dd9c..07e60f9 100644 --- a/ebml_derive/src/lib.rs +++ b/ebml_derive/src/lib.rs @@ -1,63 +1,150 @@ -use proc_macro::{Delimiter, TokenStream, TokenTree}; +use proc_macro::{token_stream, Delimiter, Span, TokenStream, TokenTree}; use quote::quote; +use syn::{Fields, FieldsUnnamed, Ident, Variant}; struct Tag { id: u64, - path: Vec<String>, - name: String, + global: bool, + path: Vec<u64>, + name: Ident, r#type: Option<String>, // None -> Master } #[proc_macro] pub fn define_ebml(ts: TokenStream) -> TokenStream { - let mut next_glob = false; let mut ts = ts.into_iter(); - let mut tags = vec![]; + parse_kt(&mut tags, &mut ts, vec![]); - while let Some(t) = ts.next() { - match t { - TokenTree::Ident(ident) => { - let ident = ident.to_string(); - if &ident == "global" { - next_glob = true; - } else { - let glob = next_glob; - next_glob = false; - let id = if let Some(TokenTree::Group(gr)) = ts.next() { - assert_eq!(gr.delimiter(), Delimiter::Bracket); - let mut ts = gr.stream().into_iter(); - if let TokenTree::Literal(lit) = ts.next().unwrap() { - u64::from_str_radix(&lit.to_string()[2..], 16).unwrap() - } else { - panic!("literal expected") - } - } else { - panic!("group expected") - }; - if let Some(TokenTree::Punct(p)) = ts.next() { - assert_eq!(p.as_char(), ':') - } else { - panic!("colon expected") - } - match ts.next() { - Some(TokenTree::Group(_)) => {} - Some(TokenTree::Ident(ident)) => { - let r#type = ident.to_string(); - eprintln!("global={glob} id={id}, type={}", r#type); + let enum_variants = tags + .iter() + .map(|e| Variant { + ident: e.name.clone(), + attrs: vec![], + fields: Fields::Unnamed( + syn::parse2::<FieldsUnnamed>(match e.r#type.clone() { + None => quote!((Master)), + Some(r#type) => match r#type.as_str() { + "Uint" => quote!((u64)), + "Utf8" => quote!((String)), + "Binary" => quote!((Vec<u8>)), + _ => panic!("unsupported type {}", r#type), + }, + }) + .expect("parse type"), + ), + discriminant: None, + }) + .collect::<Vec<_>>(); + + let path_match = tags + .iter() + .map(|e| { + let name = &e.name; + let mut path = e.path.clone(); + path.reverse(); + if e.global { + quote! { Self::#name(_) => None } + } else { + quote! { Self::#name(_) => Some(&[#(#path),*]) } + } + }) + .collect::<Vec<_>>(); + + let parse_match = tags + .iter() + .map(|Tag { id, name, .. }| { + quote! { #id => Self::#name(crate::ValueFromBuf::from_buf(data)?) } + }) + .collect::<Vec<_>>(); + + quote! { + use crate::Master; + pub enum MatroskaTag { + #(#enum_variants),* + } + impl MatroskaTag { + /// returns path in **reverse** order or None if global. + pub fn path(&self) -> Option<&'static [u64]> { + match self { #(#path_match),* } + } + pub fn parse(id: u64, data: &[u8]) -> anyhow::Result<Self> { + Ok(match id { #(#parse_match),*, _ => anyhow::bail!("unknown id") }) + } + } + } + .into() +} + +fn parse_kt(tags: &mut Vec<Tag>, ts: &mut token_stream::IntoIter, path: Vec<u64>) { + let mut next_glob = false; + loop { + let global = next_glob; + next_glob = false; - } - _ => panic!("group or ident expected"), - } - if let Some(TokenTree::Punct(p)) = ts.next() { - assert_eq!(p.as_char(), ',') - } else { - panic!("colon expected") - } + let name = if let Some(tt) = ts.next() { + if let TokenTree::Ident(name) = tt { + if &name.to_string() == "global" { + next_glob = true; + continue; } + name.to_string() + } else { + panic!("expected ident") } - x => panic!("unexpected {x:?}"), + } else { + break; + }; + + let id = if let Some(TokenTree::Group(gr)) = ts.next() { + assert_eq!(gr.delimiter(), Delimiter::Bracket); + let mut ts = gr.stream().into_iter(); + if let TokenTree::Literal(lit) = ts.next().unwrap() { + u64::from_str_radix(&lit.to_string()[2..], 16).unwrap() + } else { + panic!("literal expected") + } + } else { + panic!("group expected") + }; + if let Some(TokenTree::Punct(p)) = ts.next() { + assert_eq!(p.as_char(), ':') + } else { + panic!("colon expected") + } + match ts.next() { + Some(TokenTree::Group(gr)) => { + // eprintln!("entering group"); + let mut ts = gr.stream().into_iter(); + tags.push(Tag { + global, + id, + name: Ident::new(&name, Span::call_site().into()), + path: path.clone(), + r#type: None, + }); + let mut path = path.clone(); + path.push(id); + parse_kt(tags, &mut ts, path); + // eprintln!("leaving group"); + } + Some(TokenTree::Ident(r#type)) => { + let r#type = r#type.to_string(); + // eprintln!("global={global} id={id}, type={}", r#type); + tags.push(Tag { + id, + name: Ident::new(&name, Span::call_site().into()), + path: path.clone(), + global, + r#type: Some(r#type), + }) + } + _ => panic!("group or ident expected"), + } + if let Some(TokenTree::Punct(p)) = ts.next() { + assert_eq!(p.as_char(), ',') + } else { + panic!("colon expected") } } - quote! {}.into() } |