use proc_macro::{Delimiter, TokenStream, TokenTree};
pub struct Field {
pub name: String,
pub label: String,
pub ty: String,
pub elem: Option<String>,
pub id: bool,
pub kind: Option<&'static str>,
pub vector: Option<usize>,
}
pub struct Struct {
pub name: String,
pub fields: Vec<Field>,
}
pub fn parse(input: TokenStream) -> Result<Struct, String> {
let mut c = Cursor::new(input);
take_marks(&mut c)?;
skip_vis(&mut c);
match c.bump() {
Some(TokenTree::Ident(word)) if word.to_string() == "struct" => {}
Some(TokenTree::Ident(word)) => {
return Err(format!(
"Yo writes the shape of a struct with named fields, and this is a {word}"
));
}
_ => return Err("Yo writes the shape of a struct with named fields".to_owned()),
}
let name = match c.bump() {
Some(TokenTree::Ident(name)) => name.to_string(),
_ => return Err("this struct has no name".to_owned()),
};
let body = match c.bump() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => g.stream(),
Some(TokenTree::Punct(p)) if p.as_char() == '<' => {
return Err(format!(
"{name} has type parameters, and a shape is one description that six languages compute identically rather than one per instantiation, so Yo cannot write it"
));
}
_ => {
return Err(format!(
"{name} has no named fields, and a shape is its fields and their order"
));
}
};
Ok(Struct {
name,
fields: fields(body)?,
})
}
fn fields(body: TokenStream) -> Result<Vec<Field>, String> {
let mut c = Cursor::new(body);
let mut out: Vec<Field> = Vec::new();
while c.peek().is_some() {
let marks = take_marks(&mut c)?;
skip_vis(&mut c);
let name = match c.bump() {
Some(TokenTree::Ident(name)) => name.to_string(),
Some(other) => return Err(format!("{other} is where a field name should be")),
None => break,
};
match c.bump() {
Some(TokenTree::Punct(p)) if p.as_char() == ':' => {}
_ => {
return Err(format!(
"the field {name} has no type, and a shape is its types"
));
}
}
let (ty, elem) = take_type(&mut c);
let label = name.strip_prefix("r#").unwrap_or(&name).to_owned();
let mut field = Field {
name,
label,
ty,
elem,
id: false,
kind: None,
vector: None,
};
for mark in marks {
apply(&mut field, &mark)?;
}
out.push(field);
}
Ok(out)
}
fn apply(field: &mut Field, mark: &str) -> Result<(), String> {
if let Some(width) = mark.strip_prefix("vector=") {
let dim = width.parse::<usize>().map_err(|_| {
format!(
"the field {} asks for a vector index {width} wide, and a width is a whole number",
field.label
)
})?;
if dim == 0 {
return Err(format!(
"the field {} asks for a vector index of no width, and there is nothing to compare",
field.label
));
}
if let Some(already) = field.kind {
return Err(format!(
"the field {} asks for a vector index and a {already} one at once, and an embedding is not a key",
field.label
));
}
field.vector = Some(dim);
return Ok(());
}
let kind = match mark {
"id" => {
field.id = true;
return Ok(());
}
"index" => "Equality",
"ordered" => "Ordered",
"array" => "Array",
"text" => "Text",
"vector" => {
return Err(format!(
"the field {} asks for a vector index without saying how wide, as in #[yo(vector = 384)]",
field.label
));
}
other => {
return Err(format!(
"{other} is not something yo understands on the field {}. It knows id, index, ordered, array, text and vector",
field.label
));
}
};
if let Some(already) = field.kind {
return Err(format!(
"the field {} asks for two indexes at once, {already} and {kind}, and a path answers one question",
field.label
));
}
if field.vector.is_some() {
return Err(format!(
"the field {} asks for a vector index and a {kind} one at once, and an embedding is not a key",
field.label
));
}
field.kind = Some(kind);
Ok(())
}
fn take_marks(c: &mut Cursor) -> Result<Vec<String>, String> {
let mut words = Vec::new();
while matches!(c.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '#') {
c.bump();
let body = match c.bump() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => g.stream(),
_ => return Err("an attribute with nothing in it".to_owned()),
};
let mut inner = Cursor::new(body);
let Some(TokenTree::Ident(name)) = inner.bump() else {
continue;
};
if name.to_string() != "yo" {
continue;
}
let list = match inner.bump() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => g.stream(),
_ => return Err("yo takes a list of words, as in #[yo(index)]".to_owned()),
};
let mut items = Cursor::new(list);
while let Some(tt) = items.bump() {
match tt {
TokenTree::Ident(word) => {
let mut mark = word.to_string();
if matches!(items.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '=') {
items.bump();
match items.bump() {
Some(TokenTree::Literal(n)) => {
mark.push('=');
mark.push_str(&n.to_string());
}
_ => {
return Err(format!(
"{mark} in a yo attribute is followed by an equals sign and nothing it can use, as in #[yo(vector = 384)]"
));
}
}
}
words.push(mark);
}
TokenTree::Punct(p) if p.as_char() == ',' => {}
other => return Err(format!("{other} is not a word yo can read in an attribute")),
}
}
}
Ok(words)
}
fn skip_vis(c: &mut Cursor) {
if matches!(c.peek(), Some(TokenTree::Ident(word)) if word.to_string() == "pub") {
c.bump();
if matches!(c.peek(), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
{
c.bump();
}
}
}
fn take_type(c: &mut Cursor) -> (String, Option<String>) {
let mut depth = 0i32;
let mut ty: Vec<TokenTree> = Vec::new();
while let Some(tt) = c.peek() {
match tt {
TokenTree::Punct(p) if p.as_char() == ',' && depth == 0 => {
c.bump();
break;
}
TokenTree::Punct(p) if p.as_char() == '<' => depth += 1,
TokenTree::Punct(p) if p.as_char() == '>' && depth > 0 => depth -= 1,
_ => {}
}
if let Some(tt) = c.bump() {
ty.push(tt);
}
}
let elem = element(&ty);
(render(&ty), elem)
}
fn element(ty: &[TokenTree]) -> Option<String> {
let (first, rest) = ty.split_first()?;
match first {
TokenTree::Ident(name) if name.to_string() == "Vec" => {}
_ => return None,
}
let (open, rest) = rest.split_first()?;
match open {
TokenTree::Punct(p) if p.as_char() == '<' => {}
_ => return None,
}
let (close, inner) = rest.split_last()?;
match close {
TokenTree::Punct(p) if p.as_char() == '>' => {}
_ => return None,
}
if inner.is_empty() {
return None;
}
Some(render(inner))
}
fn render(tokens: &[TokenTree]) -> String {
tokens.iter().cloned().collect::<TokenStream>().to_string()
}
struct Cursor {
tokens: Vec<TokenTree>,
at: usize,
}
impl Cursor {
fn new(stream: TokenStream) -> Cursor {
Cursor {
tokens: stream.into_iter().collect(),
at: 0,
}
}
fn peek(&self) -> Option<&TokenTree> {
self.tokens.get(self.at)
}
fn bump(&mut self) -> Option<TokenTree> {
let tt = self.tokens.get(self.at).cloned();
if tt.is_some() {
self.at += 1;
}
tt
}
}