1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use indexmap::IndexMap;
use proc_macro2::TokenStream;
use serde::{Deserialize, Serialize};

use crate::IncludeCppConfig;

/// Struct which stores multiple sets of bindings and can be serialized
/// to disk. This is used when our build system uses `autocxx_gen`; that
/// can handle multiple `include_cpp!` macros and therefore generate multiple
/// sets of Rust bindings. We can't simply `include!` those because there's
/// no (easy) way to pass their details from the codegen phase across to
/// the Rust macro phase. Instead, we use this data structure to store
/// several sets of .rs bindings in a single file, and then the macro
/// extracts the correct set of bindings at expansion time.
#[derive(Serialize, Deserialize, Default)]
pub struct MultiBindings(IndexMap<u64, String>);

use thiserror::Error;

#[derive(Error, Debug)]
pub enum MultiBindingsErr {
    #[error("unable to find the desired bindings within the archive of Rust bindings produced by the autocxx code generation phase")]
    MissingBindings,
    #[error("the stored bindings within the JSON file could not be parsed as valid Rust tokens")]
    BindingsNotParseable,
}

impl MultiBindings {
    /// Insert some generated Rust bindings into this data structure.
    pub fn insert(&mut self, config: &IncludeCppConfig, bindings: TokenStream) {
        self.0.insert(config.get_hash(), bindings.to_string());
    }

    /// Retrieves the bindings corresponding to a given [`IncludeCppConfig`].
    pub fn get(&self, config: &IncludeCppConfig) -> Result<TokenStream, MultiBindingsErr> {
        match self.0.get(&(config.get_hash())) {
            None => Err(MultiBindingsErr::MissingBindings),
            Some(bindings) => Ok(bindings
                .parse()
                .map_err(|_| MultiBindingsErr::BindingsNotParseable)?),
        }
    }
}

#[cfg(test)]
mod tests {
    use proc_macro2::Span;
    use quote::quote;
    use syn::parse_quote;

    use crate::IncludeCppConfig;

    use super::MultiBindings;

    #[test]
    fn test_multi_bindings() {
        let hexathorpe = syn::token::Pound(Span::call_site());
        let config1: IncludeCppConfig = parse_quote! {
            #hexathorpe include "a.h"
            generate!("Foo")
        };
        let config2: IncludeCppConfig = parse_quote! {
            #hexathorpe include "b.h"
            generate!("Bar")
        };
        let config3: IncludeCppConfig = parse_quote! {
            #hexathorpe include "c.h"
            generate!("Bar")
        };
        let mut multi_bindings = MultiBindings::default();
        multi_bindings.insert(&config1, quote! { first; });
        multi_bindings.insert(&config2, quote! { second; });
        let json = serde_json::to_string(&multi_bindings).unwrap();
        let multi_bindings2: MultiBindings = serde_json::from_str(&json).unwrap();
        assert_eq!(
            multi_bindings2.get(&config2).unwrap().to_string(),
            "second ;"
        );
        assert_eq!(
            multi_bindings2.get(&config1).unwrap().to_string(),
            "first ;"
        );
        assert!(multi_bindings2.get(&config3).is_err());
    }
}