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
36///
37/// External builtins can optionally specify their stack effect for type checking.
38/// This affects how the type checker validates code using the builtin:
39///
40/// - **With effect**: The type checker enforces the declared signature.
41/// Use this when you know the exact stack effect (recommended).
42///
43/// - **Without effect (`None`)**: The type checker assigns a maximally polymorphic
44/// signature `( ..a -- ..b )`, meaning "accepts any stack, produces any stack".
45/// This **disables type checking** for calls to this builtin - type errors
46/// involving this builtin will only be caught at runtime.
47///
48/// For best type safety, always provide an explicit effect when possible.
49#[derive(Debug, Clone)]
50pub struct ExternalBuiltin {
51 /// The name used in Seq code (e.g., "journal-append")
52 pub seq_name: String,
53
54 /// The symbol name for linking (e.g., "seq_actors_journal_append")
55 ///
56 /// Must contain only alphanumeric characters, underscores, and periods.
57 /// This is validated at construction time to prevent LLVM IR injection.
58 pub symbol: String,
59
60 /// Optional stack effect for type checking.
61 ///
62 /// - `Some(effect)`: Type checker enforces this signature
63 /// - `None`: Type checker uses maximally polymorphic `( ..a -- ..b )`,
64 /// effectively disabling type checking for this builtin
65 ///
66 /// **Warning**: Using `None` can hide type errors until runtime.
67 /// Prefer providing an explicit effect when the signature is known.
68 pub effect: Option<Effect>,
69}
70
71impl ExternalBuiltin {
72 /// Validate that a symbol name is safe for LLVM IR
73 ///
74 /// Valid symbols contain only: alphanumeric characters, underscores, and periods.
75 /// This prevents injection of arbitrary LLVM IR directives.
76 fn validate_symbol(symbol: &str) -> Result<(), String> {
77 if symbol.is_empty() {
78 return Err("Symbol name cannot be empty".to_string());
79 }
80 for c in symbol.chars() {
81 if !c.is_alphanumeric() && c != '_' && c != '.' {
82 return Err(format!(
83 "Invalid character '{}' in symbol '{}'. \
84 Symbols may only contain alphanumeric characters, underscores, and periods.",
85 c, symbol
86 ));
87 }
88 }
89 Ok(())
90 }
91
92 /// Create a new external builtin with just name and symbol
93 ///
94 /// # Panics
95 ///
96 /// Panics if the symbol contains invalid characters for LLVM IR.
97 /// Valid symbols contain only alphanumeric characters, underscores, and periods.
98 pub fn new(seq_name: impl Into<String>, symbol: impl Into<String>) -> Self {
99 let symbol = symbol.into();
100 Self::validate_symbol(&symbol).expect("Invalid symbol name");
101 ExternalBuiltin {
102 seq_name: seq_name.into(),
103 symbol,
104 effect: None,
105 }
106 }
107
108 /// Create a new external builtin with a stack effect
109 ///
110 /// # Panics
111 ///
112 /// Panics if the symbol contains invalid characters for LLVM IR.
113 pub fn with_effect(
114 seq_name: impl Into<String>,
115 symbol: impl Into<String>,
116 effect: Effect,
117 ) -> Self {
118 let symbol = symbol.into();
119 Self::validate_symbol(&symbol).expect("Invalid symbol name");
120 ExternalBuiltin {
121 seq_name: seq_name.into(),
122 symbol,
123 effect: Some(effect),
124 }
125 }
126}
127
128/// Configuration for the Seq compiler
129///
130/// Allows external projects to extend the compiler with additional
131/// builtins and configuration options.
132#[derive(Debug, Clone, Default)]
133pub struct CompilerConfig {
134 /// External builtins to include in compilation
135 pub external_builtins: Vec<ExternalBuiltin>,
136
137 /// Additional library paths for linking
138 pub library_paths: Vec<String>,
139
140 /// Additional libraries to link
141 pub libraries: Vec<String>,
142
143 /// External FFI manifest paths to load
144 ///
145 /// These manifests are loaded in addition to any `include ffi:*` statements
146 /// in the source code. Use this to provide custom FFI bindings without
147 /// embedding them in the compiler.
148 pub ffi_manifest_paths: Vec<PathBuf>,
149
150 /// Pure inline test mode: bypass scheduler, return top of stack as exit code.
151 /// Only supports inline operations (integers, arithmetic, stack ops).
152 /// Used for testing and benchmarking pure computation without FFI overhead.
153 pub pure_inline_test: bool,
154}
155
156impl CompilerConfig {
157 /// Create a new empty configuration
158 pub fn new() -> Self {
159 CompilerConfig::default()
160 }
161
162 /// Add an external builtin (builder pattern)
163 pub fn with_builtin(mut self, builtin: ExternalBuiltin) -> Self {
164 self.external_builtins.push(builtin);
165 self
166 }
167
168 /// Add multiple external builtins
169 pub fn with_builtins(mut self, builtins: impl IntoIterator<Item = ExternalBuiltin>) -> Self {
170 self.external_builtins.extend(builtins);
171 self
172 }
173
174 /// Add a library path for linking
175 pub fn with_library_path(mut self, path: impl Into<String>) -> Self {
176 self.library_paths.push(path.into());
177 self
178 }
179
180 /// Add a library to link
181 pub fn with_library(mut self, lib: impl Into<String>) -> Self {
182 self.libraries.push(lib.into());
183 self
184 }
185
186 /// Add an external FFI manifest path
187 ///
188 /// The manifest will be loaded and its functions made available
189 /// during compilation, in addition to any `include ffi:*` statements.
190 pub fn with_ffi_manifest(mut self, path: impl Into<PathBuf>) -> Self {
191 self.ffi_manifest_paths.push(path.into());
192 self
193 }
194
195 /// Add multiple external FFI manifest paths
196 pub fn with_ffi_manifests(mut self, paths: impl IntoIterator<Item = PathBuf>) -> Self {
197 self.ffi_manifest_paths.extend(paths);
198 self
199 }
200
201 /// Get seq names of all external builtins (for AST validation)
202 pub fn external_names(&self) -> Vec<&str> {
203 self.external_builtins
204 .iter()
205 .map(|b| b.seq_name.as_str())
206 .collect()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_external_builtin_new() {
216 let builtin = ExternalBuiltin::new("my-func", "runtime_my_func");
217 assert_eq!(builtin.seq_name, "my-func");
218 assert_eq!(builtin.symbol, "runtime_my_func");
219 assert!(builtin.effect.is_none());
220 }
221
222 #[test]
223 fn test_config_builder() {
224 let config = CompilerConfig::new()
225 .with_builtin(ExternalBuiltin::new("func-a", "sym_a"))
226 .with_builtin(ExternalBuiltin::new("func-b", "sym_b"))
227 .with_library_path("/custom/lib")
228 .with_library("myruntime");
229
230 assert_eq!(config.external_builtins.len(), 2);
231 assert_eq!(config.library_paths, vec!["/custom/lib"]);
232 assert_eq!(config.libraries, vec!["myruntime"]);
233 }
234
235 #[test]
236 fn test_external_names() {
237 let config = CompilerConfig::new()
238 .with_builtin(ExternalBuiltin::new("func-a", "sym_a"))
239 .with_builtin(ExternalBuiltin::new("func-b", "sym_b"));
240
241 let names = config.external_names();
242 assert_eq!(names, vec!["func-a", "func-b"]);
243 }
244
245 #[test]
246 fn test_symbol_validation_valid() {
247 // Valid symbols: alphanumeric, underscores, periods
248 let _ = ExternalBuiltin::new("test", "valid_symbol");
249 let _ = ExternalBuiltin::new("test", "valid.symbol.123");
250 let _ = ExternalBuiltin::new("test", "ValidCamelCase");
251 let _ = ExternalBuiltin::new("test", "seq_actors_journal_append");
252 }
253
254 #[test]
255 #[should_panic(expected = "Invalid symbol name")]
256 fn test_symbol_validation_rejects_hyphen() {
257 // Hyphens are not valid in LLVM symbols
258 let _ = ExternalBuiltin::new("test", "invalid-symbol");
259 }
260
261 #[test]
262 #[should_panic(expected = "Invalid symbol name")]
263 fn test_symbol_validation_rejects_at() {
264 // @ could be used for LLVM IR injection
265 let _ = ExternalBuiltin::new("test", "@malicious");
266 }
267
268 #[test]
269 #[should_panic(expected = "Invalid symbol name")]
270 fn test_symbol_validation_rejects_empty() {
271 let _ = ExternalBuiltin::new("test", "");
272 }
273}