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}