Skip to main content

a3s_acl/
lib.rs

1// ============================================================================
2// ACL - Agent Configuration Language
3//
4// A3S-native configuration language for defining agent configurations.
5// ============================================================================
6
7pub mod ast;
8pub mod generator;
9pub mod lexer;
10pub mod parser;
11
12pub use ast::{Block, Document, Value};
13pub use generator::{generate, generate_from_map, Generator, GeneratorConfig};
14pub use lexer::{Lexer, Location, Span, Token, TokenWithSpan};
15pub use parser::{parse, ParseError};
16
17/// Parse ACL text into a Document
18///
19/// # Example
20///
21/// ```
22/// use a3s_acl::parse;
23///
24/// let input = r#"
25///     default_model = "openai/gpt-4"
26///
27///     providers "openai" {
28///         name = "openai"
29///         api_key = "sk-test"
30///         base_url = "https://api.openai.com/v1"
31///     }
32/// "#;
33///
34/// let doc = parse(input).unwrap();
35/// for block in doc.blocks {
36///     println!("Block: {}", block.name);
37/// }
38/// ```
39pub fn parse_acl(input: &str) -> Result<Document, ParseError> {
40    parse(input)
41}
42
43/// Generate ACL text from a Document
44///
45/// # Example
46///
47/// ```
48/// use a3s_acl::{generate, Document, Block, Value};
49/// use std::collections::HashMap;
50///
51/// let mut attrs = HashMap::new();
52/// attrs.insert("name".to_string(), Value::String("test".to_string()));
53///
54/// let doc = Document {
55///     blocks: vec![Block {
56///         name: "config".to_string(),
57///         labels: vec![],
58///         blocks: vec![],
59///         attributes: attrs,
60///     }],
61/// };
62///
63/// let output = generate(&doc);
64/// println!("{}", output);
65/// ```
66pub fn generate_acl(doc: &Document) -> String {
67    generate(doc)
68}
69
70/// High-level builder API for creating ACL configurations
71pub mod builder {
72    use crate::ast::{Block, Document, Value};
73    use std::collections::HashMap;
74
75    /// Builder for ACL Documents
76    pub struct DocumentBuilder {
77        blocks: Vec<Block>,
78    }
79
80    impl DocumentBuilder {
81        pub fn new() -> Self {
82            Self { blocks: Vec::new() }
83        }
84
85        /// Add a block to the document
86        pub fn block(mut self, block: Block) -> Self {
87            self.blocks.push(block);
88            self
89        }
90
91        /// Add a simple key-value block
92        pub fn kv_block(mut self, name: &str, key: &str, value: Value) -> Self {
93            let mut attrs = HashMap::new();
94            attrs.insert(key.to_string(), value);
95            self.blocks.push(Block {
96                name: name.to_string(),
97                labels: Vec::new(),
98                blocks: Vec::new(),
99                attributes: attrs,
100            });
101            self
102        }
103
104        /// Build the document
105        pub fn build(self) -> Document {
106            Document {
107                blocks: self.blocks,
108            }
109        }
110    }
111
112    impl Default for DocumentBuilder {
113        fn default() -> Self {
114            Self::new()
115        }
116    }
117
118    /// Builder for Blocks
119    pub struct BlockBuilder {
120        name: String,
121        labels: Vec<String>,
122        blocks: Vec<Block>,
123        attributes: HashMap<String, Value>,
124    }
125
126    impl BlockBuilder {
127        pub fn new(name: &str) -> Self {
128            Self {
129                name: name.to_string(),
130                labels: Vec::new(),
131                blocks: Vec::new(),
132                attributes: HashMap::new(),
133            }
134        }
135
136        pub fn label(mut self, label: &str) -> Self {
137            self.labels.push(label.to_string());
138            self
139        }
140
141        pub fn attr(mut self, key: &str, value: Value) -> Self {
142            self.attributes.insert(key.to_string(), value);
143            self
144        }
145
146        pub fn nested_block(mut self, block: Block) -> Self {
147            self.blocks.push(block);
148            self
149        }
150
151        pub fn build(self) -> Block {
152            Block {
153                name: self.name,
154                labels: self.labels,
155                blocks: self.blocks,
156                attributes: self.attributes,
157            }
158        }
159    }
160
161    // Helper functions for creating Values
162    pub fn string(s: &str) -> Value {
163        Value::String(s.to_string())
164    }
165
166    pub fn number(n: f64) -> Value {
167        Value::Number(n)
168    }
169
170    pub fn integer(n: i64) -> Value {
171        Value::Number(n as f64)
172    }
173
174    pub fn boolean(b: bool) -> Value {
175        Value::Bool(b)
176    }
177
178    pub fn null() -> Value {
179        Value::Null
180    }
181
182    pub fn list(items: Vec<Value>) -> Value {
183        Value::List(items)
184    }
185
186    pub fn call(name: &str, args: Vec<Value>) -> Value {
187        Value::Call(name.to_string(), args)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_parse_acl_function() {
197        let input = r#"
198            name = "test"
199            count = 42
200        "#;
201        let doc = parse_acl(input).unwrap();
202        assert_eq!(doc.blocks.len(), 2);
203    }
204
205    #[test]
206    fn test_generate_acl_function() {
207        let doc = Document {
208            blocks: vec![Block {
209                name: "test".to_string(),
210                labels: vec![],
211                blocks: vec![],
212                attributes: vec![("key".to_string(), Value::String("value".to_string()))]
213                    .into_iter()
214                    .collect(),
215            }],
216        };
217        let output = generate_acl(&doc);
218        assert!(output.contains("test"));
219        assert!(output.contains("value"));
220    }
221
222    #[test]
223    fn test_builder_document_builder() {
224        let doc = builder::DocumentBuilder::new()
225            .kv_block("config", "key", builder::string("value"))
226            .build();
227
228        assert_eq!(doc.blocks.len(), 1);
229        assert_eq!(doc.blocks[0].name, "config");
230    }
231
232    #[test]
233    fn test_builder_document_builder_with_block() {
234        let block = builder::BlockBuilder::new("provider")
235            .attr("name", builder::string("openai"))
236            .build();
237
238        let doc = builder::DocumentBuilder::new().block(block).build();
239
240        assert_eq!(doc.blocks.len(), 1);
241        assert_eq!(doc.blocks[0].name, "provider");
242    }
243
244    #[test]
245    fn test_builder_document_default() {
246        let doc = builder::DocumentBuilder::default().build();
247        assert!(doc.blocks.is_empty());
248    }
249
250    #[test]
251    fn test_builder_block_builder() {
252        let block = builder::BlockBuilder::new("provider")
253            .label("openai")
254            .attr("api_key", builder::string("sk-xxx"))
255            .attr("enabled", builder::boolean(true))
256            .build();
257
258        assert_eq!(block.name, "provider");
259        assert_eq!(block.labels, vec!["openai"]);
260        assert_eq!(
261            block
262                .attributes
263                .get("api_key")
264                .map(|v| v.to_string())
265                .unwrap(),
266            "sk-xxx"
267        );
268    }
269
270    #[test]
271    fn test_builder_block_builder_nested() {
272        let inner = builder::BlockBuilder::new("model")
273            .attr("name", builder::string("gpt-4"))
274            .build();
275
276        let block = builder::BlockBuilder::new("provider")
277            .label("openai")
278            .nested_block(inner)
279            .build();
280
281        assert_eq!(block.blocks.len(), 1);
282        assert_eq!(block.blocks[0].name, "model");
283    }
284
285    #[test]
286    fn test_builder_value_helpers() {
287        assert_eq!(builder::string("test"), Value::String("test".to_string()));
288        assert_eq!(builder::number(3.14), Value::Number(3.14));
289        assert_eq!(builder::integer(42), Value::Number(42.0));
290        assert_eq!(builder::boolean(true), Value::Bool(true));
291        assert_eq!(builder::boolean(false), Value::Bool(false));
292        assert_eq!(builder::null(), Value::Null);
293        assert_eq!(
294            builder::list(vec![Value::Number(1.0)]),
295            Value::List(vec![Value::Number(1.0)])
296        );
297    }
298
299    #[test]
300    fn test_re_exports() {
301        // Verify re-exported types are accessible
302        let _ = Document::default();
303        let _ = Block {
304            name: "test".to_string(),
305            labels: vec![],
306            blocks: vec![],
307            attributes: std::collections::HashMap::new(),
308        };
309        let _ = Value::String("test".to_string());
310        let _ = Generator::new();
311        let _ = GeneratorConfig::default();
312        let _ = Lexer::new("");
313        let _ = Location {
314            line: 1,
315            column: 1,
316            offset: 0,
317        };
318        let _ = Span::default();
319        let _ = Token::Eof;
320        let _ = TokenWithSpan::new(Token::Eof, Location::default(), Location::default());
321        let _ = ParseError {
322            message: "test".to_string(),
323            line: 1,
324            column: 1,
325        };
326    }
327
328    #[test]
329    fn test_lexer_re_exports() {
330        let mut lexer = Lexer::new("test = 42");
331        let tokens = lexer.tokenize();
332        assert!(!tokens.is_empty());
333    }
334
335    #[test]
336    fn test_token_with_span() {
337        let loc = Location {
338            line: 1,
339            column: 1,
340            offset: 0,
341        };
342        let token = TokenWithSpan::new(Token::Ident("test".to_string()), loc, loc);
343        assert!(matches!(token.token, Token::Ident(_)));
344    }
345}