opendp_tooling/codegen/
mod.rs

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

use crate::{Argument, TypeRecipe};

pub mod python;
pub mod r;

pub fn write_bindings(base_dir: PathBuf, files: HashMap<PathBuf, String>) {
    for (file_path, file_contents) in files {
        File::create(base_dir.join(file_path))
            .unwrap()
            .write_all(file_contents.as_ref())
            .unwrap();
    }
}

fn tab(number_of_spaces: usize, text: String) -> String {
    text.split('\n')
        .map(|v| {
            if v.is_empty() {
                String::new()
            } else {
                format!("{}{}", " ".repeat(number_of_spaces), v)
            }
        })
        .collect::<Vec<_>>()
        .join("\n")
}

pub(crate) fn tab_r(text: String) -> String {
    tab(2, text)
}

pub(crate) fn tab_py(text: String) -> String {
    tab(4, text)
}

pub(crate) fn tab_c(text: String) -> String {
    tab(4, text)
}

/// resolve references to derived types
fn flatten_type_recipe(type_recipe: &TypeRecipe, derived_types: &Vec<Argument>) -> TypeRecipe {
    let resolve_name = |name: &String| {
        derived_types
            .iter()
            .find(|derived| derived.name.as_ref().unwrap() == name)
            .map(|derived_type| {
                flatten_type_recipe(derived_type.rust_type.as_ref().unwrap(), derived_types)
            })
            .unwrap_or_else(|| type_recipe.clone())
    };

    match type_recipe {
        TypeRecipe::Name(name) => resolve_name(name),
        TypeRecipe::Nest { origin, args } => TypeRecipe::Nest {
            origin: origin.clone(),
            args: args
                .iter()
                .map(|arg| flatten_type_recipe(arg, derived_types))
                .collect(),
        },
        other => other.clone(),
    }
}

impl Argument {
    /// retrieve the python ctype corresponding to the type inside FfiResult<*>
    pub fn python_unwrapped_ctype(&self, typemap: &HashMap<String, String>) -> String {
        let c_type = self.c_type();
        assert_eq!(&c_type[..9], "FfiResult");
        typemap
            .get(&c_type[10..c_type.len() - 1])
            .expect(format!("unrecognized c_type: {c_type}").as_str())
            .clone()
    }
    /// retrieve the python ctypes corresponding to the origin of a type (subtypes/args omitted)
    pub fn python_origin_ctype(&self, typemap: &HashMap<String, String>) -> String {
        typemap.get(&self.c_type_origin()).cloned().expect(&format!(
            "ctype not recognized in typemap: {:?}",
            self.c_type_origin()
        ))
    }
    pub fn python_type_hint(&self, hierarchy: &HashMap<String, Vec<String>>) -> Option<String> {
        if self.hint.is_some() {
            return self.hint.clone();
        }
        if self.is_type {
            return Some("RuntimeTypeDescriptor".to_string());
        }
        if let Some(TypeRecipe::Nest { origin, args }) = &self.rust_type {
            if origin == "Tuple" {
                return Some(format!("tuple[{}]", vec!["Any"; args.len()].join(", ")));
            }
        }
        self.c_type.clone().and_then(|mut c_type| {
            if c_type.starts_with("FfiResult<") {
                c_type = c_type[10..c_type.len() - 1].to_string();
            }
            if c_type.ends_with("AnyTransformation *") {
                return Some("Transformation".to_string());
            }
            if c_type.ends_with("AnyMeasurement *") {
                return Some("Measurement".to_string());
            }
            if c_type.ends_with("AnyFunction *") {
                return Some("Function".to_string());
            }
            if c_type.ends_with("AnyDomain *") {
                return Some("Domain".to_string());
            }
            if c_type.ends_with("AnyMetric *") {
                return Some("Metric".to_string());
            }
            if c_type.ends_with("AnyMeasure *") {
                return Some("Measure".to_string());
            }
            if c_type.ends_with("AnyObject *") || c_type.ends_with("FfiSlice *") {
                // Returning "Any" doesn't strengthen type checking,
                // and sometimes seems odd in the docs.
                return None;
            }

            hierarchy
                .iter()
                .find(|(_k, members)| members.contains(&c_type))
                .and_then(|(k, _)| {
                    Some(match k.as_str() {
                        k if k == "integer" => "int",
                        k if k == "float" => "float",
                        k if k == "string" => "str",
                        k if k == "bool" => "bool",
                        _ => return None,
                    })
                })
                .map(|v| v.to_string())
        })
    }
}

impl Argument {
    pub fn name(&self) -> String {
        self.name
            .clone()
            .expect("unknown name when parsing argument")
    }
    pub fn c_type(&self) -> String {
        if self.is_type {
            return "char *".to_string();
        }
        self.c_type
            .clone()
            .expect("unknown c_type when parsing argument")
    }
    pub fn c_type_origin(&self) -> String {
        self.c_type().split('<').next().unwrap().to_string()
    }
}

#[cfg(test)]
mod test;