~chiefnoah/bare_proc

685864efb2aeac6f66753e9a19e344727aa965a9 — Noah Pederson 7 months ago
Initial commit

Migrated from the multi-crate repo at:

  https://git.sr.ht/~chiefnoah/bare-rs

See that repo for more history. Splits up the crates to be a little
cleaner and more focused.
9 files changed, 948 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.lock
A Cargo.toml
A LICENSE
A README.md
A src/example.bare
A src/grammar.pest
A src/lib.rs
A src/parser.rs
A  => .gitignore +3 -0
@@ 1,3 @@
target/
.idea/
.direnv/

A  => Cargo.lock +215 -0
@@ 1,215 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "bare_proc"
version = "0.1.0"
dependencies = [
 "pest",
 "pest_derive",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
 "generic-array",
]

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
 "libc",
]

[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
 "generic-array",
 "typenum",
]

[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
 "block-buffer",
 "crypto-common",
]

[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
 "typenum",
 "version_check",
]

[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"

[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"

[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"

[[package]]
name = "pest"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
dependencies = [
 "memchr",
 "thiserror",
 "ucd-trie",
]

[[package]]
name = "pest_derive"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c"
dependencies = [
 "pest",
 "pest_generator",
]

[[package]]
name = "pest_generator"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd"
dependencies = [
 "pest",
 "pest_meta",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "pest_meta"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca"
dependencies = [
 "once_cell",
 "pest",
 "sha2",
]

[[package]]
name = "proc-macro2"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
 "cfg-if",
 "cpufeatures",
 "digest",
]

[[package]]
name = "syn"
version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "thiserror"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"

[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"

[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

A  => Cargo.toml +18 -0
@@ 1,18 @@
[package]
name = "bare_proc"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
quote = "1.0.35"
syn = "2.0.48"
proc-macro2 = "1.0.78"
pest = { version = "2.7.6" }
pest_derive = "2.7.6"

[features]

A  => LICENSE +23 -0
@@ 1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

A  => README.md +42 -0
@@ 1,42 @@
# bare-rs

`bare-rs` is a proc-macro that implements a parser-generator for the
[BARE message format](https://datatracker.ietf.org/doc/draft-devault-bare/).

It relies on [`serde`](https://serde.rs/) using
[`serde_bare`](https://git.sr.ht/~tdeo/serde_bare) to implement serialization.

## Usage

Define you BARE schema in a .bare file:

```
type User struct {
  name: str
  key: data[128]
  id: uint
}
```

Then in a corresponding Rust file:

```rust
bare_schema!("schema.bare");
```

which will expand roughly the following:

```rust
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct User {
    name: String,
    key: [u8; 128],
    id: u64,
}
```



## License

`bare-rs` is licensed under MIT.

A  => src/example.bare +40 -0
@@ 1,40 @@
type PrivKey data
type PublicKey data[128]
type Time str # ISO 8601

type Department enum {
  ACCOUNTING
  ADMINISTRATION
  CUSTOMER_SERVICE
  DEVELOPMENT

  # Reserved for the CEO
  JSMITH = 99
}

type Address list<str>[4] # street, city, state, country

type Customer struct {
  name: str
  email: str
  address: Address
  orders: list<struct {
    orderId: i64
    quantity: i32
  }>
  metadata: map<str><data>
}

type Employee struct {
  name: str
  email: str
  address: Address
  department: Department
  hireDate: Time
  publicKey: optional<PublicKey>
  metadata: map<str><data>
}

type TerminatedEmployee void

type Person union {Customer | Employee | TerminatedEmployee}

A  => src/grammar.pest +46 -0
@@ 1,46 @@
//schema            =  { SOI ~ user_type+ ~ EOI }
schema            =  { SOI ~ user_type+ ~ EOI }
type_l            = _{ "type" }
user_type         =  { "type" ~ user_type_name ~ any_type }
user_type_name    = @{ ASCII_ALPHA_UPPER ~ (ASCII_ALPHANUMERIC | "_" | "-")* }
unsigned_t        = { "uint" | "u64" | "u32" | "u16" | "u8" }
signed_t          = { "int" | "i64" | "i32" | "i16" | "i8" }
void_t            = { "void" }
str_t             = { "str" }
bool_t            = { "bool" }
float_t           = { "f32" | "f64" }
data_t            = { "data" ~ (length)? }
enum_t            = { "enum" ~ "{" ~ enum_value+ ~ "}" }
enum_value        =  { enum_value_name ~ ("=" ~ integer)? }
enum_value_name   = @{ ASCII_ALPHA_UPPER ~ (ASCII_ALPHANUMERIC | "_" | "-")* }
list_t            =  { "list" ~ type_t ~ length? }
type_t            =  _{ "<" ~ any_type ~ ">" }
struct_t          =  { "struct" ~ "{" ~ struct_field+ ~ "}" }
map_t             =  { "map" ~ type_t ~ type_t }
union_t           =  { "union" ~ "{" ~ any_type ~ ("|" ~ any_type)* ~ "}" }
optional_t        =  { "optional" ~ type_t }
struct_field      =  { struct_field_name ~ ":" ~ any_type }
struct_field_name =  { (ASCII_ALPHANUMERIC | "_" | "-")+ }
length            =  _{ "[" ~ integer ~ "]" }
integer           = !{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
primative_type      =  _{
    unsigned_t
  | signed_t
  | bool_t
  | float_t
  | data_t
  | str_t
  | void_t
}
any_type          = _{
    user_type_name
  | list_t
  | struct_t
  | enum_t
  | map_t
  | union_t
  | optional_t
  | primative_type
}
WHITESPACE        = _{ " " | "\n" | NEWLINE }
COMMENT           = _{ "#" ~ (!NEWLINE ~ ANY)* ~ NEWLINE }

A  => src/lib.rs +333 -0
@@ 1,333 @@
mod parser;
use std::{collections::BTreeMap, fs::read_to_string};

use parser::{parse_string, AnyType, PrimativeType, StructField};
use proc_macro;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{parse_macro_input, ExprLit};

type DefRegistry = Vec<TokenStream>;

fn ident_from_string(s: &String) -> Ident {
    Ident::new(s, Span::call_site())
}

#[proc_macro]
pub fn bare_schema(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    // TODO: support Into<PathBuf>
    let input = parse_macro_input!(item as ExprLit);
    let path = match input.lit {
        syn::Lit::Str(s) => s,
        _ => panic!("Unexpected literal type, expected string"),
    };
    let file = read_to_string(path.value()).unwrap();
    let user_type_registry: BTreeMap<String, AnyType> = parse_string(&file);
    let mut user_type_syntax = DefRegistry::new();
    for (name, user_type) in user_type_registry {
        user_type_syntax = gen_user_type(user_type_syntax, &name, &user_type);
    }

    quote! {
        use std::io::{Error as IOError, Read, Write};
        use serde::{Serialize, Deserialize};
        use std::collections::HashMap;

        #(#user_type_syntax)*
    }
    .into()
}

/// `gen_user_type` is responsible for generating the token streams of a single user type at a top
/// level. Rust does not support anonymous structs/enums/etc., so we must recursively parse any
/// anonymous definitions and generate top-level definitions. As such, this function may generate
/// multiple types.
fn gen_user_type(mut registry: DefRegistry, name: &String, t: &AnyType) -> DefRegistry {
    #[allow(unused_assignments)]
    let mut def = TokenStream::new();
    use AnyType::*;
    (registry, def) = match t {
        Primative(p) => {
            def = gen_primative_type_def(p);
            let ident = ident_from_string(name);
            (
                registry,
                quote! {
                    type #ident = #def;
                },
            )
        }
        List { inner, length } => {
            (registry, def) = gen_list(registry, name, inner.as_ref(), length);
            let ident = ident_from_string(name);
            (
                registry,
                quote! {
                    type #ident = #def;
                },
            )
        }
        Struct(fields) => {
            (registry, _) = gen_struct(registry, name, fields);
            // `gen_struct` only has side-effects on the registry, so we return nothing
            (registry, TokenStream::new())
        }
        Map { key, value } => {
            let (registry, map_def) = gen_map(registry, name, key.as_ref(), value.as_ref());
            let ident = ident_from_string(name);
            (
                registry,
                quote! {
                    type #ident = #map_def;
                },
            )
        }
        Optional(inner) => {
            let (registry, inner_def) = dispatch_type(registry, name, inner);
            let ident = ident_from_string(name);
            (
                registry,
                quote! {
                    type #ident = #inner_def;
                },
            )
        }
        TypeReference(reference) => {
            panic!("Type reference is not valid as a top level definition: {reference}")
        }
        Enum(members) => {
            (registry, _) = gen_enum(registry, name, members);
            // `gen_enum` only has side-effects on the registry, so we return nothing
            (registry, TokenStream::new())
        }
        Union(members) => {
            (registry, _) = gen_union(registry, name, members);
            // `gen_union` only has side-effects on the registry, so we return nothing
            (registry, TokenStream::new())
        }
    };
    registry.push(def);
    registry
}

fn dispatch_type(
    registry: DefRegistry,
    name: &String,
    any_type: &AnyType,
) -> (DefRegistry, TokenStream) {
    match any_type {
        AnyType::Primative(p) => (registry, gen_primative_type_def(p)),
        AnyType::List { inner, length } => gen_list(registry, name, inner.as_ref(), length),
        AnyType::Struct(fields) => gen_struct(registry, name, fields),
        AnyType::Enum(members) => gen_enum(registry, name, members),
        AnyType::Map { key, value } => gen_map(registry, name, key.as_ref(), value.as_ref()),
        AnyType::Union(members) => gen_union(registry, name, members),
        AnyType::Optional(inner) => gen_option(registry, name, inner),
        AnyType::TypeReference(i) => {
            let ident = ident_from_string(i);
            (registry, quote! { #ident })
        }
    }
}

fn gen_map(
    registry: DefRegistry,
    name: &String,
    key: &AnyType,
    value: &AnyType,
) -> (DefRegistry, TokenStream) {
    let (registry, key_def) = dispatch_type(registry, name, key);
    let (registry, val_def) = dispatch_type(registry, name, value);
    (
        registry,
        quote! {
            HashMap<#key_def, #val_def>
        },
    )
}

fn gen_list(
    registry: DefRegistry,
    name: &String,
    inner_type: &AnyType,
    size: &Option<usize>,
) -> (DefRegistry, TokenStream) {
    let (registry, inner_def) = dispatch_type(registry, name, inner_type);
    (
        registry,
        match *size {
            Some(size) if size <= 32 => quote! {
                [#inner_def; #size]
            },
            _ => quote! {
                Vec<#inner_def>
            },
        },
    )
}

fn gen_struct(
    registry: DefRegistry,
    name: &String,
    fields: &Vec<StructField>,
) -> (DefRegistry, TokenStream) {
    // clone so we can safely drain this
    let fields_clone = fields.clone();
    let (registry, fields_gen) = gen_struct_field(registry, name, fields_clone);
    gen_anonymous(registry, name, |ident| {
        quote! {
            #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
            struct #ident {
                #(#fields_gen),*
            }
        }
    })
}

fn gen_union(
    mut registry: DefRegistry,
    name: &String,
    members: &Vec<AnyType>,
) -> (DefRegistry, TokenStream) {
    let mut members_def: Vec<TokenStream> = Vec::with_capacity(members.len());
    for (i, member) in members.iter().enumerate() {
        // This is to allow the `registry` binding to not shadow the function arg, but instead
        // rebind it as it's used in the subsequent `gen_anonymous` call. We'll get move errors if
        // we don't do it this way.
        #[allow(unused_assignments)]
        let mut member_def = TokenStream::new();
        (registry, member_def) = match member {
            AnyType::Struct(fields) => {
                let (registry, fields_defs) = gen_struct_field(registry, name, fields.clone());
                (
                    registry,
                    quote! {
                        {
                            #(#fields_defs),*
                        }
                    },
                )
            }
            _ => {
                #[allow(unused_assignments)]
                let mut inner_def = TokenStream::new();
                (registry, inner_def) =
                    dispatch_type(registry, &format!("{name}Member{i}"), member);
                (
                    registry,
                    // The `inner_def` is always a top-level type here
                    quote! {
                        #inner_def(#inner_def)
                    },
                )
            }
        };
        members_def.push(member_def);
    }
    gen_anonymous(registry, name, |ident| {
        quote! {
            #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
            enum #ident {
                #(#members_def),*
            }
        }
    })
}

fn gen_option(registry: DefRegistry, name: &String, inner: &AnyType) -> (DefRegistry, TokenStream) {
    let (registry, inner_def) = dispatch_type(registry, name, inner);
    (
        registry,
        quote! {
           Option<#inner_def>
        },
    )
}

fn gen_struct_field(
    mut registry: DefRegistry,
    struct_name: &String,
    fields: Vec<StructField>,
) -> (DefRegistry, Vec<TokenStream>) {
    let mut fields_gen: Vec<TokenStream> = Vec::with_capacity(fields.len());
    for StructField { name, type_r } in fields {
        #[allow(unused_assignments)]
        let mut field_gen = TokenStream::new();
        (registry, field_gen) = dispatch_type(registry, &format!("{struct_name}{name}"), &type_r);
        let ident = ident_from_string(&name);
        fields_gen.push(quote! {
            #ident: #field_gen
        })
    }
    (registry, fields_gen)
}

fn gen_enum(
    registry: DefRegistry,
    name: &String,
    members: &Vec<(String, Option<usize>)>,
) -> (DefRegistry, TokenStream) {
    let member_defs = members.iter().map(|(name, val)| {
        let ident = ident_from_string(name);
        if let Some(val) = val {
            quote! {
                #ident = #val
            }
        } else {
            quote! {
                #ident
            }
        }
    });
    gen_anonymous(registry, name, |ident| {
        quote! {
            #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
            #[repr(usize)]
            enum #ident {
                #(#member_defs),*
            }
        }
    })
}

/// `gen_anonymous` generates an identifier from the provided `name`, passed it to `inner`, pushes
/// the result of `inner` to the `registry`, and yields a quoted version of the generated
/// identifier. This is a common operation when generating types that are anonymous in a BARE
/// schema but not allowed by be defined anonymously in Rust.
fn gen_anonymous(
    mut registry: Vec<TokenStream>,
    name: &String,
    inner: impl FnOnce(Ident) -> TokenStream,
) -> (Vec<TokenStream>, TokenStream) {
    let ident = ident_from_string(name);
    registry.push(inner(ident.clone()));
    (
        registry,
        quote! {
            #ident
        },
    )
}

fn gen_primative_type_def(p: &PrimativeType) -> TokenStream {
    use PrimativeType::*;
    match p {
        UInt | U64 => quote! { u64 },
        U32 => quote! { u32 },
        U16 => quote! { u16 },
        U8 => quote! { u8 },
        Int | I64 => quote! { i64 },
        I32 => quote! { i32 },
        I16 => quote! { i16 },
        I8 => quote! { i8 },
        F64 => quote! { f64 },
        F32 => quote! { f32 },
        Str => quote! { String },
        Data(s) => match s {
            Some(size) if *size <= 32 => quote! { [u8; #size] },
            _ => quote! { Vec<u8> },
        },
        Void => quote! { () },
        Bool => quote! { bool },
    }
}

A  => src/parser.rs +228 -0
@@ 1,228 @@
#![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);
        }
    }
}