bare_proc 0.2.0

A proc macro to generate Rust types from a BARE schema file.
Documentation
#![allow(dead_code)]
use pest::{iterators::Pair, Parser};
use pest_derive::Parser;
use std::collections::BTreeMap;

#[derive(Parser)]
#[grammar = "grammar.pest"]
struct BARE;

pub type Length = usize;

#[derive(Debug, Clone, Copy)]
pub enum PrimativeType {
    UInt,
    U64,
    U32,
    U16,
    U8,
    Int,
    I64,
    I32,
    I16,
    I8,
    F64,
    F32,
    Str,
    Data(Option<Length>),
    Void,
    Bool,
}

#[derive(Debug, Clone)]
pub enum AnyType {
    Primative(PrimativeType),
    List {
        inner: Box<AnyType>,
        length: Option<usize>,
    },
    Struct(Vec<StructField>),
    Enum(Vec<(String, Option<usize>)>),
    Map {
        key: Box<AnyType>,
        value: Box<AnyType>,
    },
    Union(Vec<AnyType>),
    Optional(Box<AnyType>),
    TypeReference(String),
}

#[derive(Debug, Clone)]
pub struct StructField {
    pub name: String,
    pub type_r: AnyType,
}

pub fn parse_string(schema: &str) -> BTreeMap<String, AnyType> {
    let schema = BARE::parse(Rule::schema, &schema)
        .unwrap_or_else(|e| panic!("{}", e))
        .next()
        .unwrap(); // this can't fail if parsing didn't fail
    if schema.as_rule() != Rule::schema {
        unreachable!()
    }
    let mut user_type_registry: BTreeMap<String, AnyType> = BTreeMap::default();
    for user_types in schema.into_inner() {
        if user_types.as_rule() == Rule::EOI {
            break;
        }
        let mut inner = user_types.into_inner();
        let user_type_name = inner.next().unwrap();
        let user_type_type = inner.next().unwrap();
        if user_type_registry.contains_key(user_type_name.as_str()) {
            panic!("Duplicate definition: {:?}", user_type_name.as_span())
        }
        let t = parse_any_type(&user_type_registry, user_type_type);
        user_type_registry.insert(user_type_name.as_str().into(), t);
    }
    user_type_registry
}

fn parse_any_type(registry: &BTreeMap<String, AnyType>, pair: Pair<'_, Rule>) -> AnyType {
    match pair.as_rule() {
        Rule::unsigned_t => parse_unsigned_int(pair),
        Rule::signed_t => parse_signed_int(pair),
        Rule::void_t => AnyType::Primative(PrimativeType::Void),
        Rule::str_t => AnyType::Primative(PrimativeType::Str),
        Rule::bool_t => AnyType::Primative(PrimativeType::Bool),
        Rule::float_t => parse_float(pair),
        Rule::data_t => {
            let length_t = pair.into_inner().next();
            if let Some(length_t) = length_t {
                let length: usize = length_t.as_str().parse().unwrap();
                AnyType::Primative(PrimativeType::Data(Some(length)))
            } else {
                AnyType::Primative(PrimativeType::Data(None))
            }
        }
        Rule::enum_t => parse_enum(pair),
        Rule::list_t => parse_list(registry, pair),
        Rule::struct_t => parse_struct(registry, pair),
        Rule::map_t => parse_map(registry, pair),
        Rule::union_t => parse_union(registry, pair),
        Rule::optional_t => {
            let inner_type = pair.into_inner().next().unwrap();
            AnyType::Optional(Box::new(parse_any_type(registry, inner_type)))
        }
        Rule::user_type_name => {
            let user_type = pair.as_str();
            if registry.contains_key(user_type) {
                AnyType::TypeReference(user_type.into())
            } else {
                panic!("User type {user_type} has not been defined yet.");
            }
        }
        x => panic!("Unreachable: {x:?}"),
    }
}

fn parse_unsigned_int(pair: Pair<'_, Rule>) -> AnyType {
    assert!(pair.as_rule() == Rule::unsigned_t);
    AnyType::Primative(match pair.as_str() {
        "uint" => PrimativeType::UInt,
        "u64" => PrimativeType::U64,
        "u32" => PrimativeType::U32,
        "u16" => PrimativeType::U16,
        "u8" => PrimativeType::U8,
        _ => unreachable!(),
    })
}

fn parse_signed_int(pair: Pair<'_, Rule>) -> AnyType {
    assert!(pair.as_rule() == Rule::signed_t);
    AnyType::Primative(match pair.as_str() {
        "int" => PrimativeType::Int,
        "i64" => PrimativeType::I64,
        "i32" => PrimativeType::I32,
        "i16" => PrimativeType::I16,
        "i8" => PrimativeType::I8,
        _ => unreachable!(),
    })
}

fn parse_enum(pair: Pair<'_, Rule>) -> AnyType {
    let mut members: Vec<(String, Option<Length>)> = Vec::new();
    for enum_value in pair.into_inner() {
        assert!(enum_value.as_rule() == Rule::enum_value);
        let mut e = enum_value.into_inner();
        let enum_value_name = e.next().unwrap();
        let value: Option<usize> = e.next().and_then(|e| Some(e.as_str().parse().unwrap()));
        members.push((enum_value_name.as_str().into(), value));
    }
    AnyType::Enum(members)
}

fn parse_list(registry: &BTreeMap<String, AnyType>, pair: Pair<'_, Rule>) -> AnyType {
    let mut list = pair.into_inner();
    let list_type = list.next().unwrap();
    let inner = parse_any_type(registry, list_type);
    let length: Option<usize> = list
        .next()
        .and_then(|e: Pair<'_, Rule>| Some(e.as_str().parse().unwrap()));
    AnyType::List {
        inner: Box::new(inner),
        length,
    }
}

fn parse_struct(registry: &BTreeMap<String, AnyType>, pair: Pair<'_, Rule>) -> AnyType {
    let mut st = pair.into_inner();
    let mut fields: Vec<StructField> = Vec::new();
    while let Some(struct_t) = st.next() {
        let mut struct_field = struct_t.into_inner();
        let field_name = struct_field.next().unwrap();
        let field_type = struct_field.next().unwrap();
        let ft = parse_any_type(registry, field_type);
        fields.push(StructField {
            name: field_name.as_str().to_string(),
            type_r: ft,
        })
    }
    AnyType::Struct(fields)
}
fn parse_map(registry: &BTreeMap<String, AnyType>, pair: Pair<'_, Rule>) -> AnyType {
    let mut map = pair.into_inner();
    let key_t = map.next().unwrap();
    let value_t = map.next().unwrap();
    let key = parse_any_type(registry, key_t);
    let value = parse_any_type(registry, value_t);
    AnyType::Map {
        key: Box::new(key),
        value: Box::new(value),
    }
}
fn parse_union(registry: &BTreeMap<String, AnyType>, pair: Pair<'_, Rule>) -> AnyType {
    let union_t = pair.into_inner();
    let mut members: Vec<AnyType> = Vec::new();
    for union_member in union_t {
        let t = parse_any_type(registry, union_member);
        members.push(t);
    }
    AnyType::Union(members)
}

fn parse_float(pair: Pair<'_, Rule>) -> AnyType {
    assert!(pair.as_rule() == Rule::float_t);
    AnyType::Primative(match pair.as_str() {
        "f32" => PrimativeType::F32,
        "f64" => PrimativeType::F64,
        _ => unreachable!(),
    })
}

#[cfg(test)]
mod test {
    use std::fs::read_to_string;

    use super::*;

    #[test]
    fn basic() {
        let file = read_to_string("./src/example.bare").unwrap();
        let user_type_registry: BTreeMap<String, AnyType> = parse_string(&file);

        for (key, value) in user_type_registry.iter() {
            println!("{} = {:?}", key, value);
        }
    }
}