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