Skip to main content

typr_core/
abstractions.rs

1//! Abstraction traits for platform-independent operations
2//!
3//! These traits allow typr-core to work both natively (with filesystem access)
4//! and in WebAssembly (with in-memory sources).
5
6use std::collections::HashMap;
7
8/// Provides source code content for compilation.
9///
10/// This trait abstracts away file system access, allowing the compiler
11/// to work with in-memory sources (useful for WASM and testing).
12pub trait SourceProvider {
13    /// Get the source code for a given file path
14    fn get_source(&self, path: &str) -> Option<String>;
15
16    /// Check if a source file exists
17    fn exists(&self, path: &str) -> bool {
18        self.get_source(path).is_some()
19    }
20
21    /// List available source files (for module resolution)
22    fn list_sources(&self) -> Vec<String> {
23        vec![]
24    }
25}
26
27/// In-memory source provider for WASM and testing
28#[derive(Debug, Clone, Default)]
29pub struct InMemorySourceProvider {
30    sources: HashMap<String, String>,
31}
32
33impl InMemorySourceProvider {
34    /// Create a new empty source provider
35    pub fn new() -> Self {
36        Self {
37            sources: HashMap::new(),
38        }
39    }
40
41    /// Add a source file
42    pub fn add_source(&mut self, path: &str, content: &str) {
43        self.sources.insert(path.to_string(), content.to_string());
44    }
45
46    /// Add a source file (builder pattern)
47    pub fn with_source(mut self, path: &str, content: &str) -> Self {
48        self.add_source(path, content);
49        self
50    }
51
52    /// Remove a source file
53    pub fn remove_source(&mut self, path: &str) {
54        self.sources.remove(path);
55    }
56
57    /// Clear all sources
58    pub fn clear(&mut self) {
59        self.sources.clear();
60    }
61}
62
63impl SourceProvider for InMemorySourceProvider {
64    fn get_source(&self, path: &str) -> Option<String> {
65        self.sources.get(path).cloned()
66    }
67
68    fn exists(&self, path: &str) -> bool {
69        self.sources.contains_key(path)
70    }
71
72    fn list_sources(&self) -> Vec<String> {
73        self.sources.keys().cloned().collect()
74    }
75}
76
77/// Handles output from transpilation.
78///
79/// This trait allows customizing where transpiled R code goes -
80/// to files, memory buffers, or anywhere else.
81pub trait OutputHandler {
82    /// Write transpiled R code
83    fn write_r_code(&mut self, filename: &str, content: &str) -> Result<(), OutputError>;
84
85    /// Write type annotations
86    fn write_type_annotations(&mut self, filename: &str, content: &str) -> Result<(), OutputError>;
87
88    /// Write generic function declarations
89    fn write_generic_functions(&mut self, filename: &str, content: &str)
90        -> Result<(), OutputError>;
91}
92
93/// In-memory output handler for WASM and testing
94#[derive(Debug, Clone, Default)]
95pub struct InMemoryOutputHandler {
96    pub outputs: HashMap<String, String>,
97}
98
99impl InMemoryOutputHandler {
100    pub fn new() -> Self {
101        Self {
102            outputs: HashMap::new(),
103        }
104    }
105
106    pub fn get_output(&self, filename: &str) -> Option<&String> {
107        self.outputs.get(filename)
108    }
109}
110
111impl OutputHandler for InMemoryOutputHandler {
112    fn write_r_code(&mut self, filename: &str, content: &str) -> Result<(), OutputError> {
113        self.outputs
114            .insert(filename.to_string(), content.to_string());
115        Ok(())
116    }
117
118    fn write_type_annotations(&mut self, filename: &str, content: &str) -> Result<(), OutputError> {
119        self.outputs
120            .insert(format!("{}_types", filename), content.to_string());
121        Ok(())
122    }
123
124    fn write_generic_functions(
125        &mut self,
126        filename: &str,
127        content: &str,
128    ) -> Result<(), OutputError> {
129        self.outputs
130            .insert(format!("{}_generics", filename), content.to_string());
131        Ok(())
132    }
133}
134
135/// Output operation error
136#[derive(Debug, Clone)]
137pub struct OutputError {
138    pub message: String,
139}
140
141impl std::fmt::Display for OutputError {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(f, "Output error: {}", self.message)
144    }
145}
146
147impl std::error::Error for OutputError {}
148
149/// Checks and optionally installs R packages.
150///
151/// In native mode, this can execute R commands.
152/// In WASM mode, this can be a no-op or return cached information.
153pub trait PackageChecker {
154    /// Check if a package is available
155    fn is_package_available(&self, name: &str) -> bool;
156
157    /// Try to install a package (may be a no-op in WASM)
158    fn install_package(&mut self, name: &str) -> Result<(), PackageError>;
159
160    /// Get package type information (cached)
161    fn get_package_types(&self, name: &str) -> Option<String>;
162}
163
164/// Stub package checker that does nothing (for WASM)
165#[derive(Debug, Clone, Default)]
166pub struct StubPackageChecker {
167    available_packages: HashMap<String, String>,
168}
169
170impl StubPackageChecker {
171    pub fn new() -> Self {
172        Self {
173            available_packages: HashMap::new(),
174        }
175    }
176
177    /// Pre-register a package with its type information
178    pub fn register_package(&mut self, name: &str, types: &str) {
179        self.available_packages
180            .insert(name.to_string(), types.to_string());
181    }
182}
183
184impl PackageChecker for StubPackageChecker {
185    fn is_package_available(&self, name: &str) -> bool {
186        self.available_packages.contains_key(name)
187    }
188
189    fn install_package(&mut self, _name: &str) -> Result<(), PackageError> {
190        // No-op in WASM - packages must be pre-registered
191        Ok(())
192    }
193
194    fn get_package_types(&self, name: &str) -> Option<String> {
195        self.available_packages.get(name).cloned()
196    }
197}
198
199/// Package operation error
200#[derive(Debug, Clone)]
201pub struct PackageError {
202    pub message: String,
203}
204
205impl std::fmt::Display for PackageError {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        write!(f, "Package error: {}", self.message)
208    }
209}
210
211impl std::error::Error for PackageError {}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    #[test]
218    fn test_in_memory_source_provider() {
219        let mut provider = InMemorySourceProvider::new();
220        provider.add_source("test.ty", "let x: Number = 42;");
221
222        assert!(provider.exists("test.ty"));
223        assert!(!provider.exists("nonexistent.ty"));
224        assert_eq!(
225            provider.get_source("test.ty"),
226            Some("let x: Number = 42;".to_string())
227        );
228    }
229
230    #[test]
231    fn test_builder_pattern() {
232        let provider = InMemorySourceProvider::new()
233            .with_source("a.ty", "let a = 1;")
234            .with_source("b.ty", "let b = 2;");
235
236        assert!(provider.exists("a.ty"));
237        assert!(provider.exists("b.ty"));
238    }
239}