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);
+ }
+ }
+}