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}