Skip to main content

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    /// Bake per-word atomic call counters into the binary.
171    /// When true, each word entry point gets an `atomicrmw add` counter.
172    /// Use with `SEQ_REPORT=words` to see call counts at exit.
173    pub instrument: bool,
174}
175
176impl CompilerConfig {
177    /// Create a new empty configuration
178    pub fn new() -> Self {
179        CompilerConfig::default()
180    }
181
182    /// Add an external builtin (builder pattern)
183    pub fn with_builtin(mut self, builtin: ExternalBuiltin) -> Self {
184        self.external_builtins.push(builtin);
185        self
186    }
187
188    /// Add multiple external builtins
189    pub fn with_builtins(mut self, builtins: impl IntoIterator<Item = ExternalBuiltin>) -> Self {
190        self.external_builtins.extend(builtins);
191        self
192    }
193
194    /// Add a library path for linking
195    pub fn with_library_path(mut self, path: impl Into<String>) -> Self {
196        self.library_paths.push(path.into());
197        self
198    }
199
200    /// Add a library to link
201    pub fn with_library(mut self, lib: impl Into<String>) -> Self {
202        self.libraries.push(lib.into());
203        self
204    }
205
206    /// Add an external FFI manifest path
207    ///
208    /// The manifest will be loaded and its functions made available
209    /// during compilation, in addition to any `include ffi:*` statements.
210    pub fn with_ffi_manifest(mut self, path: impl Into<PathBuf>) -> Self {
211        self.ffi_manifest_paths.push(path.into());
212        self
213    }
214
215    /// Add multiple external FFI manifest paths
216    pub fn with_ffi_manifests(mut self, paths: impl IntoIterator<Item = PathBuf>) -> Self {
217        self.ffi_manifest_paths.extend(paths);
218        self
219    }
220
221    /// Set the optimization level for compilation
222    pub fn with_optimization_level(mut self, level: OptimizationLevel) -> Self {
223        self.optimization_level = level;
224        self
225    }
226
227    /// Get seq names of all external builtins (for AST validation)
228    pub fn external_names(&self) -> Vec<&str> {
229        self.external_builtins
230            .iter()
231            .map(|b| b.seq_name.as_str())
232            .collect()
233    }
234}
235
236#[cfg(test)]
237#[allow(deprecated)] // Tests for the deprecated ExternalBuiltin::new method
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_external_builtin_new() {
243        let builtin = ExternalBuiltin::new("my-func", "runtime_my_func");
244        assert_eq!(builtin.seq_name, "my-func");
245        assert_eq!(builtin.symbol, "runtime_my_func");
246        assert!(builtin.effect.is_none());
247    }
248
249    #[test]
250    fn test_config_builder() {
251        let config = CompilerConfig::new()
252            .with_builtin(ExternalBuiltin::new("func-a", "sym_a"))
253            .with_builtin(ExternalBuiltin::new("func-b", "sym_b"))
254            .with_library_path("/custom/lib")
255            .with_library("myruntime");
256
257        assert_eq!(config.external_builtins.len(), 2);
258        assert_eq!(config.library_paths, vec!["/custom/lib"]);
259        assert_eq!(config.libraries, vec!["myruntime"]);
260    }
261
262    #[test]
263    fn test_external_names() {
264        let config = CompilerConfig::new()
265            .with_builtin(ExternalBuiltin::new("func-a", "sym_a"))
266            .with_builtin(ExternalBuiltin::new("func-b", "sym_b"));
267
268        let names = config.external_names();
269        assert_eq!(names, vec!["func-a", "func-b"]);
270    }
271
272    #[test]
273    fn test_symbol_validation_valid() {
274        // Valid symbols: alphanumeric, underscores, periods
275        let _ = ExternalBuiltin::new("test", "valid_symbol");
276        let _ = ExternalBuiltin::new("test", "valid.symbol.123");
277        let _ = ExternalBuiltin::new("test", "ValidCamelCase");
278        let _ = ExternalBuiltin::new("test", "seq_actors_journal_append");
279    }
280
281    #[test]
282    #[should_panic(expected = "Invalid symbol name")]
283    fn test_symbol_validation_rejects_hyphen() {
284        // Hyphens are not valid in LLVM symbols
285        let _ = ExternalBuiltin::new("test", "invalid-symbol");
286    }
287
288    #[test]
289    #[should_panic(expected = "Invalid symbol name")]
290    fn test_symbol_validation_rejects_at() {
291        // @ could be used for LLVM IR injection
292        let _ = ExternalBuiltin::new("test", "@malicious");
293    }
294
295    #[test]
296    #[should_panic(expected = "Invalid symbol name")]
297    fn test_symbol_validation_rejects_empty() {
298        let _ = ExternalBuiltin::new("test", "");
299    }
300}