Skip to main content

a3s_acl/
ast.rs

1// ============================================================================
2// ACL AST - Abstract Syntax Tree for Agent Configuration Language
3// ============================================================================
4
5use std::collections::HashMap;
6
7/// An ACL configuration document
8#[derive(Debug, Clone, Default)]
9pub struct Document {
10    pub blocks: Vec<Block>,
11}
12
13/// A named block like `providers { }` or `models { }`
14#[derive(Debug, Clone)]
15pub struct Block {
16    /// Block type name (e.g., "providers", "models")
17    pub name: String,
18    /// Optional block labels (e.g., `"openai"` in `providers "openai" { }`)
19    pub labels: Vec<String>,
20    /// Nested blocks inside this block
21    pub blocks: Vec<Block>,
22    /// Attribute assignments
23    pub attributes: HashMap<String, Value>,
24}
25
26/// A value in ACL (string, number, bool, list, etc.)
27#[derive(Debug, Clone, PartialEq)]
28pub enum Value {
29    /// A string value: "hello"
30    String(String),
31    /// A number value: 42 or 3.14
32    Number(f64),
33    /// A boolean value: true or false
34    Bool(bool),
35    /// A list value: [1, 2, 3]
36    List(Vec<Value>),
37    /// An object/block value: { key = "value" }
38    Object(Vec<(String, Value)>),
39    /// Null value
40    Null,
41    /// A function call: env("VAR") or concat("a", "b")
42    Call(String, Vec<Value>),
43}
44
45impl Value {
46    /// Get the string value if this is a String variant
47    pub fn as_str(&self) -> Option<&str> {
48        match self {
49            Value::String(s) => Some(s),
50            _ => None,
51        }
52    }
53
54    /// Get the number value if this is a Number variant
55    pub fn as_number(&self) -> Option<f64> {
56        match self {
57            Value::Number(n) => Some(*n),
58            _ => None,
59        }
60    }
61
62    /// Get the bool value if this is a Bool variant
63    pub fn as_bool(&self) -> Option<bool> {
64        match self {
65            Value::Bool(b) => Some(*b),
66            _ => None,
67        }
68    }
69
70    /// Check if this is a Null value
71    pub fn is_null(&self) -> bool {
72        matches!(self, Value::Null)
73    }
74
75    /// Check if this is a string value
76    pub fn is_string(&self) -> bool {
77        matches!(self, Value::String(_))
78    }
79}
80
81impl std::fmt::Display for Value {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            Value::String(s) => write!(f, "{}", s),
85            Value::Number(n) => {
86                if n.fract() == 0.0 {
87                    write!(f, "{}", *n as i64)
88                } else {
89                    write!(f, "{}", n)
90                }
91            }
92            Value::Bool(b) => write!(f, "{}", b),
93            Value::List(items) => {
94                let items_str: Vec<String> = items.iter().map(|v| v.to_string()).collect();
95                write!(f, "[{}]", items_str.join(", "))
96            }
97            Value::Object(pairs) => {
98                let pairs_str: Vec<String> = pairs
99                    .iter()
100                    .map(|(k, v)| format!("{} = {}", k, v))
101                    .collect();
102                write!(f, "{{{}}}", pairs_str.join(", "))
103            }
104            Value::Null => write!(f, "null"),
105            Value::Call(name, args) => {
106                let args_str: Vec<String> = args.iter().map(|v| v.to_string()).collect();
107                write!(f, "{}({})", name, args_str.join(", "))
108            }
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_value_as_str() {
119        assert_eq!(Value::String("hello".to_string()).as_str(), Some("hello"));
120        assert_eq!(Value::Number(42.0).as_str(), None);
121        assert_eq!(Value::Bool(true).as_str(), None);
122        assert_eq!(Value::Null.as_str(), None);
123    }
124
125    #[test]
126    fn test_value_as_number() {
127        assert_eq!(Value::Number(42.0).as_number(), Some(42.0));
128        assert_eq!(Value::String("hello".to_string()).as_number(), None);
129        assert_eq!(Value::Bool(true).as_number(), None);
130        assert_eq!(Value::Null.as_number(), None);
131    }
132
133    #[test]
134    fn test_value_as_bool() {
135        assert_eq!(Value::Bool(true).as_bool(), Some(true));
136        assert_eq!(Value::Bool(false).as_bool(), Some(false));
137        assert_eq!(Value::String("hello".to_string()).as_bool(), None);
138        assert_eq!(Value::Number(42.0).as_bool(), None);
139    }
140
141    #[test]
142    fn test_value_is_null() {
143        assert!(Value::Null.is_null());
144        assert!(!Value::String("hello".to_string()).is_null());
145        assert!(!Value::Number(42.0).is_null());
146        assert!(!Value::Bool(true).is_null());
147    }
148
149    #[test]
150    fn test_value_is_string() {
151        assert!(Value::String("hello".to_string()).is_string());
152        assert!(!Value::Number(42.0).is_string());
153        assert!(!Value::Bool(true).is_string());
154        assert!(!Value::Null.is_string());
155    }
156
157    #[test]
158    fn test_value_display() {
159        assert_eq!(Value::String("hello".to_string()).to_string(), "hello");
160        assert_eq!(Value::Number(42.0).to_string(), "42");
161        assert_eq!(Value::Number(3.14).to_string(), "3.14");
162        assert_eq!(Value::Bool(true).to_string(), "true");
163        assert_eq!(Value::Bool(false).to_string(), "false");
164        assert_eq!(Value::Null.to_string(), "null");
165        assert_eq!(Value::List(vec![]).to_string(), "[]");
166        assert_eq!(
167            Value::List(vec![Value::Number(1.0), Value::Number(2.0)]).to_string(),
168            "[1, 2]"
169        );
170        assert_eq!(Value::Object(vec![]).to_string(), "{}");
171        assert_eq!(
172            Value::Object(vec![("a".to_string(), Value::Number(1.0))]).to_string(),
173            "{a = 1}"
174        );
175    }
176
177    #[test]
178    fn test_document_default() {
179        let doc = Document::default();
180        assert!(doc.blocks.is_empty());
181    }
182
183    #[test]
184    fn test_block_clone() {
185        let mut attrs = HashMap::new();
186        attrs.insert("key".to_string(), Value::String("value".to_string()));
187        let block = Block {
188            name: "test".to_string(),
189            labels: vec!["label".to_string()],
190            blocks: vec![],
191            attributes: attrs,
192        };
193        let cloned = block.clone();
194        assert_eq!(cloned.name, "test");
195        assert_eq!(cloned.labels, vec!["label"]);
196    }
197
198    #[test]
199    fn test_value_clone() {
200        let value = Value::String("test".to_string());
201        let cloned = value.clone();
202        assert_eq!(value, cloned);
203    }
204
205    #[test]
206    fn test_value_partial_eq() {
207        assert_eq!(
208            Value::String("a".to_string()),
209            Value::String("a".to_string())
210        );
211        assert_ne!(
212            Value::String("a".to_string()),
213            Value::String("b".to_string())
214        );
215        assert_eq!(Value::Number(42.0), Value::Number(42.0));
216        assert_ne!(Value::Number(42.0), Value::Number(43.0));
217        assert_eq!(Value::Bool(true), Value::Bool(true));
218        assert_ne!(Value::Bool(true), Value::Bool(false));
219        assert_eq!(Value::Null, Value::Null);
220    }
221}