seqc/
config.rs

1//! Compiler configuration for extensibility
2//!
3//! This module provides configuration types that allow external projects
4//! to extend the Seq compiler with additional builtins without modifying
5//! the core compiler.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use seqc::{CompilerConfig, ExternalBuiltin};
11//!
12//! // Define builtins provided by your runtime extension
13//! let config = CompilerConfig::new()
14//!     .with_builtin(ExternalBuiltin::new(
15//!         "journal-append",
16//!         "my_runtime_journal_append",
17//!     ))
18//!     .with_builtin(ExternalBuiltin::new(
19//!         "actor-send",
20//!         "my_runtime_actor_send",
21//!     ));
22//!
23//! // Compile with extended builtins
24//! compile_file_with_config(source_path, output_path, false, &config)?;
25//! ```
26
27use crate::types::Effect;
28use std::path::PathBuf;
29
30/// Definition of an external builtin function
31///
32/// External builtins are functions provided by a runtime extension
33/// (like an actor system) that should be callable from Seq code.
34///
35/// # Type Safety (v2.0)
36///
37/// All external builtins **must** specify their stack effect for type checking.
38/// The compiler will error if an external builtin is registered without an effect.
39///
40/// Use [`ExternalBuiltin::with_effect`] to create builtins with explicit effects.
41#[derive(Debug, Clone)]
42pub struct ExternalBuiltin {
43    /// The name used in Seq code (e.g., "journal-append")
44    pub seq_name: String,
45
46    /// The symbol name for linking (e.g., "seq_actors_journal_append")
47    ///
48    /// Must contain only alphanumeric characters, underscores, and periods.
49    /// This is validated at construction time to prevent LLVM IR injection.
50    pub symbol: String,
51
52    /// Stack effect for type checking (required as of v2.0).
53    ///
54    /// The type checker enforces this signature at all call sites.
55    /// The compiler will error if this is `None`.
56    pub effect: Option<Effect>,
57}
58
59impl ExternalBuiltin {
60    /// Validate that a symbol name is safe for LLVM IR
61    ///
62    /// Valid symbols contain only: alphanumeric characters, underscores, and periods.
63    /// This prevents injection of arbitrary LLVM IR directives.
64    fn validate_symbol(symbol: &str) -> Result<(), String> {
65        if symbol.is_empty() {
66            return Err("Symbol name cannot be empty".to_string());
67        }
68        for c in symbol.chars() {
69            if !c.is_alphanumeric() && c != '_' && c != '.' {
70                return Err(format!(
71                    "Invalid character '{}' in symbol '{}'. \
72                     Symbols may only contain alphanumeric characters, underscores, and periods.",
73                    c, symbol
74                ));
75            }
76        }
77        Ok(())
78    }
79
80    /// Create a new external builtin with just name and symbol (deprecated)
81    ///
82    /// # Deprecated
83    ///
84    /// As of v2.0, all external builtins must have explicit stack effects.
85    /// Use [`ExternalBuiltin::with_effect`] instead. Builtins created with
86    /// this method will cause a compiler error.
87    ///
88    /// # Panics
89    ///
90    /// Panics if the symbol contains invalid characters for LLVM IR.
91    /// Valid symbols contain only alphanumeric characters, underscores, and periods.
92    #[deprecated(
93        since = "2.0.0",
94        note = "Use with_effect instead - effects are now required"
95    )]
96    pub fn new(seq_name: impl Into<String>, symbol: impl Into<String>) -> Self {
97        let symbol = symbol.into();
98        Self::validate_symbol(&symbol).expect("Invalid symbol name");
99        ExternalBuiltin {
100            seq_name: seq_name.into(),
101            symbol,
102            effect: None,
103        }
104    }
105
106    /// Create a new external builtin with a stack effect
107    ///
108    /// # Panics
109    ///
110    /// Panics if the symbol contains invalid characters for LLVM IR.
111    pub fn with_effect(
112        seq_name: impl Into<String>,
113        symbol: impl Into<String>,
114        effect: Effect,
115    ) -> Self {
116        let symbol = symbol.into();
117        Self::validate_symbol(&symbol).expect("Invalid symbol name");
118        ExternalBuiltin {
119            seq_name: seq_name.into(),
120            symbol,
121            effect: Some(effect),
122        }
123    }
124}
125
126/// Configuration for the Seq compiler
127///
128/// Allows external projects to extend the compiler with additional
129/// builtins and configuration options.
130#[derive(Debug, Clone, Default)]
131pub struct CompilerConfig {
132    /// External builtins to include in compilation
133    pub external_builtins: Vec<ExternalBuiltin>,
134
135    /// Additional library paths for linking
136    pub library_paths: Vec<String>,
137
138    /// Additional libraries to link
139    pub libraries: Vec<String>,
140
141    /// External FFI manifest paths to load
142    ///
143    /// These manifests are loaded in addition to any `include ffi:*` statements
144    /// in the source code. Use this to provide custom FFI bindings without
145    /// embedding them in the compiler.
146    pub ffi_manifest_paths: Vec<PathBuf>,
147
148    /// Pure inline test mode: bypass scheduler, return top of stack as exit code.
149    /// Only supports inline operations (integers, arithmetic, stack ops).
150    /// Used for testing and benchmarking pure computation without FFI overhead.
151    pub pure_inline_test: bool,
152}
153
154impl CompilerConfig {
155    /// Create a new empty configuration
156    pub fn new() -> Self {
157        CompilerConfig::default()
158    }
159
160    /// Add an external builtin (builder pattern)
161    pub fn with_builtin(mut self, builtin: ExternalBuiltin) -> Self {
162        self.external_builtins.push(builtin);
163        self
164    }
165
166    /// Add multiple external builtins
167    pub fn with_builtins(mut self, builtins: impl IntoIterator<Item = ExternalBuiltin>) -> Self {
168        self.external_builtins.extend(builtins);
169        self
170    }
171
172    /// Add a library path for linking
173    pub fn with_library_path(mut self, path: impl Into<String>) -> Self {
174        self.library_paths.push(path.into());
175        self
176    }
177
178    /// Add a library to link
179    pub fn with_library(mut self, lib: impl Into<String>) -> Self {
180        self.libraries.push(lib.into());
181        self
182    }
183
184    /// Add an external FFI manifest path
185    ///
186    /// The manifest will be loaded and its functions made available
187    /// during compilation, in addition to any `include ffi:*` statements.
188    pub fn with_ffi_manifest(mut self, path: impl Into<PathBuf>) -> Self {
189        self.ffi_manifest_paths.push(path.into());
190        self
191    }
192
193    /// Add multiple external FFI manifest paths
194    pub fn with_ffi_manifests(mut self, paths: impl IntoIterator<Item = PathBuf>) -> Self {
195        self.ffi_manifest_paths.extend(paths);
196        self
197    }
198
199    /// Get seq names of all external builtins (for AST validation)
200    pub fn external_names(&self) -> Vec<&str> {
201        self.external_builtins
202            .iter()
203            .map(|b| b.seq_name.as_str())
204            .collect()
205    }
206}
207
208#[cfg(test)]
209#[allow(deprecated)] // Tests for the deprecated ExternalBuiltin::new method
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_external_builtin_new() {
215        let builtin = ExternalBuiltin::new("my-func", "runtime_my_func");
216        assert_eq!(builtin.seq_name, "my-func");
217        assert_eq!(builtin.symbol, "runtime_my_func");
218        assert!(builtin.effect.is_none());
219    }
220
221    #[test]
222    fn test_config_builder() {
223        let config = CompilerConfig::new()
224            .with_builtin(ExternalBuiltin::new("func-a", "sym_a"))
225            .with_builtin(ExternalBuiltin::new("func-b", "sym_b"))
226            .with_library_path("/custom/lib")
227            .with_library("myruntime");
228
229        assert_eq!(config.external_builtins.len(), 2);
230        assert_eq!(config.library_paths, vec!["/custom/lib"]);
231        assert_eq!(config.libraries, vec!["myruntime"]);
232    }
233
234    #[test]
235    fn test_external_names() {
236        let config = CompilerConfig::new()
237            .with_builtin(ExternalBuiltin::new("func-a", "sym_a"))
238            .with_builtin(ExternalBuiltin::new("func-b", "sym_b"));
239
240        let names = config.external_names();
241        assert_eq!(names, vec!["func-a", "func-b"]);
242    }
243
244    #[test]
245    fn test_symbol_validation_valid() {
246        // Valid symbols: alphanumeric, underscores, periods
247        let _ = ExternalBuiltin::new("test", "valid_symbol");
248        let _ = ExternalBuiltin::new("test", "valid.symbol.123");
249        let _ = ExternalBuiltin::new("test", "ValidCamelCase");
250        let _ = ExternalBuiltin::new("test", "seq_actors_journal_append");
251    }
252
253    #[test]
254    #[should_panic(expected = "Invalid symbol name")]
255    fn test_symbol_validation_rejects_hyphen() {
256        // Hyphens are not valid in LLVM symbols
257        let _ = ExternalBuiltin::new("test", "invalid-symbol");
258    }
259
260    #[test]
261    #[should_panic(expected = "Invalid symbol name")]
262    fn test_symbol_validation_rejects_at() {
263        // @ could be used for LLVM IR injection
264        let _ = ExternalBuiltin::new("test", "@malicious");
265    }
266
267    #[test]
268    #[should_panic(expected = "Invalid symbol name")]
269    fn test_symbol_validation_rejects_empty() {
270        let _ = ExternalBuiltin::new("test", "");
271    }
272}