Skip to main content

gram_codec/
value.rs

1//! Value enum for heterogeneous property types in Gram notation
2
3use std::fmt;
4
5/// Represents all possible value types in Gram notation property records.
6/// Supports all value types defined in the tree-sitter-gram grammar.
7#[derive(Debug, Clone)]
8pub enum Value {
9    /// Unquoted or quoted string
10    /// Example: `"Alice"`, `hello`
11    String(String),
12
13    /// Integer value with full i64 range
14    /// Example: `42`, `-10`, `0`
15    Integer(i64),
16
17    /// Decimal/floating-point value
18    /// Example: `3.14`, `-2.5`, `0.0`
19    Decimal(f64),
20
21    /// Boolean value
22    /// Example: `true`, `false`
23    Boolean(bool),
24
25    /// Array of values (may be heterogeneous)
26    /// Example: `["rust", 42, true]`
27    Array(Vec<Value>),
28
29    /// Numeric range with inclusive bounds
30    /// Example: `1..10`, `0..100`
31    Range { lower: i64, upper: i64 },
32
33    /// Tagged string with format identifier
34    /// Example: `"""markdown # Heading"""`
35    TaggedString { tag: String, content: String },
36}
37
38impl Value {
39    // TODO: tree-sitter parsing methods removed during nom parser migration
40    // Value parsing is now handled by parser::value module
41
42    /* Commented out during migration to nom parser
43    pub fn from_tree_sitter_node_OLD(
44        node: &TREE_SITTER_NODE,
45        source: &str,
46    ) -> Result<Self, ParseError> {
47        match node.kind() {
48            "symbol" => {
49                let text = node
50                    .utf8_text(source.as_bytes())
51                    .map_err(|e| Self::node_parse_error(node, format!("UTF-8 error: {}", e)))?;
52                Ok(Value::String(text.to_string()))
53            }
54            "string_literal" => {
55                let content = extract_string_content(node, source)?;
56                Ok(Value::String(content))
57            }
58            "integer" => {
59                let text = node
60                    .utf8_text(source.as_bytes())
61                    .map_err(|e| Self::node_parse_error(node, format!("UTF-8 error: {}", e)))?;
62                let value = text
63                    .parse::<i64>()
64                    .map_err(|e| Self::node_parse_error(node, format!("Invalid integer: {}", e)))?;
65                Ok(Value::Integer(value))
66            }
67            "decimal" => {
68                let text = node
69                    .utf8_text(source.as_bytes())
70                    .map_err(|e| Self::node_parse_error(node, format!("UTF-8 error: {}", e)))?;
71                let value = text
72                    .parse::<f64>()
73                    .map_err(|e| Self::node_parse_error(node, format!("Invalid decimal: {}", e)))?;
74                Ok(Value::Decimal(value))
75            }
76            "boolean_literal" => {
77                let text = node
78                    .utf8_text(source.as_bytes())
79                    .map_err(|e| Self::node_parse_error(node, format!("UTF-8 error: {}", e)))?;
80                let value = text == "true";
81                Ok(Value::Boolean(value))
82            }
83            "array" => {
84                let mut values = Vec::new();
85                let mut cursor = node.walk();
86                for child in node.children(&mut cursor) {
87                    if child.is_named() && child.kind() != "," {
88                        values.push(Value::from_tree_sitter_node(&child, source)?);
89                    }
90                }
91                Ok(Value::Array(values))
92            }
93            "range" => {
94                let lower = extract_range_bound(node, "lower", source)?;
95                let upper = extract_range_bound(node, "upper", source)?;
96                Ok(Value::Range { lower, upper })
97            }
98            "tagged_string" => {
99                let (tag, content) = extract_tagged_string(node, source)?;
100                Ok(Value::TaggedString { tag, content })
101            }
102            _ => panic!("tree-sitter parsing no longer supported"),
103        }
104    }
105    */
106    // End of commented tree-sitter code
107
108    /// Serialize value to gram notation
109    pub fn to_gram_notation(&self) -> String {
110        match self {
111            // Strings are always quoted in gram notation property values
112            Value::String(s) => format!("\"{}\"", escape_string(s)),
113            Value::Integer(i) => i.to_string(),
114            Value::Decimal(f) => format_decimal(*f),
115            Value::Boolean(b) => b.to_string(),
116            Value::Array(values) => {
117                let items: Vec<String> = values.iter().map(|v| v.to_gram_notation()).collect();
118                format!("[{}]", items.join(", "))
119            }
120            Value::Range { lower, upper } => format!("{}..{}", lower, upper),
121            Value::TaggedString { tag, content } => {
122                if tag.is_empty() {
123                    format!("\"\"\"{}\"\"\"", content)
124                } else {
125                    format!("\"\"\"{}{}\"\"\"", tag, content)
126                }
127            }
128        }
129    }
130
131    /// Get the type name of this value (for error messages)
132    pub fn type_name(&self) -> &'static str {
133        match self {
134            Value::String(_) => "string",
135            Value::Integer(_) => "integer",
136            Value::Decimal(_) => "decimal",
137            Value::Boolean(_) => "boolean",
138            Value::Array(_) => "array",
139            Value::Range { .. } => "range",
140            Value::TaggedString { .. } => "tagged string",
141        }
142    }
143
144    // TODO: node_parse_error removed during migration
145}
146
147impl fmt::Display for Value {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        write!(f, "{}", self.to_gram_notation())
150    }
151}
152
153impl PartialEq for Value {
154    fn eq(&self, other: &Self) -> bool {
155        match (self, other) {
156            (Value::String(a), Value::String(b)) => a == b,
157            (Value::Integer(a), Value::Integer(b)) => a == b,
158            (Value::Decimal(a), Value::Decimal(b)) => {
159                // Use epsilon comparison for floats
160                (a - b).abs() < f64::EPSILON
161            }
162            (Value::Boolean(a), Value::Boolean(b)) => a == b,
163            (Value::Array(a), Value::Array(b)) => a == b,
164            (
165                Value::Range {
166                    lower: l1,
167                    upper: u1,
168                },
169                Value::Range {
170                    lower: l2,
171                    upper: u2,
172                },
173            ) => l1 == l2 && u1 == u2,
174            (
175                Value::TaggedString {
176                    tag: t1,
177                    content: c1,
178                },
179                Value::TaggedString {
180                    tag: t2,
181                    content: c2,
182                },
183            ) => t1 == t2 && c1 == c2,
184            _ => false,
185        }
186    }
187}
188
189// Helper Functions
190
191/// Escape special characters in strings
192pub(crate) fn escape_string(s: &str) -> String {
193    s.replace('\\', "\\\\")
194        .replace('"', "\\\"")
195        .replace('\n', "\\n")
196        .replace('\t', "\\t")
197        .replace('\r', "\\r")
198}
199
200/// Format decimal to avoid unnecessary trailing zeros while distinguishing from integers
201pub(crate) fn format_decimal(f: f64) -> String {
202    if f.fract() == 0.0 && f.is_finite() {
203        format!("{:.1}", f) // Always include .0 to distinguish from integer
204    } else {
205        f.to_string()
206    }
207}
208
209// TODO: tree-sitter helper functions removed during migration to nom parser
210// Value parsing is now handled by parser::value module
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_string_value_serialization() {
218        // Strings are always quoted in gram notation property values
219        assert_eq!(
220            Value::String("hello".to_string()).to_gram_notation(),
221            "\"hello\""
222        );
223        assert_eq!(
224            Value::String("Hello World".to_string()).to_gram_notation(),
225            "\"Hello World\""
226        );
227        assert_eq!(Value::String("".to_string()).to_gram_notation(), "\"\"");
228    }
229
230    #[test]
231    fn test_integer_value_serialization() {
232        assert_eq!(Value::Integer(42).to_gram_notation(), "42");
233        assert_eq!(Value::Integer(-10).to_gram_notation(), "-10");
234        assert_eq!(Value::Integer(0).to_gram_notation(), "0");
235    }
236
237    #[test]
238    fn test_decimal_value_serialization() {
239        assert_eq!(Value::Decimal(3.14).to_gram_notation(), "3.14");
240        assert_eq!(Value::Decimal(0.0).to_gram_notation(), "0.0");
241        assert_eq!(Value::Decimal(-2.5).to_gram_notation(), "-2.5");
242    }
243
244    #[test]
245    fn test_boolean_value_serialization() {
246        assert_eq!(Value::Boolean(true).to_gram_notation(), "true");
247        assert_eq!(Value::Boolean(false).to_gram_notation(), "false");
248    }
249
250    #[test]
251    fn test_array_value_serialization() {
252        let v = Value::Array(vec![
253            Value::Integer(1),
254            Value::Integer(2),
255            Value::Integer(3),
256        ]);
257        assert_eq!(v.to_gram_notation(), "[1, 2, 3]");
258
259        // Heterogeneous array
260        let v = Value::Array(vec![
261            Value::String("rust".to_string()),
262            Value::Integer(42),
263            Value::Boolean(true),
264        ]);
265        // Strings are always quoted
266        assert_eq!(v.to_gram_notation(), "[\"rust\", 42, true]");
267
268        // Empty array
269        assert_eq!(Value::Array(vec![]).to_gram_notation(), "[]");
270    }
271
272    #[test]
273    fn test_range_value_serialization() {
274        let v = Value::Range {
275            lower: 1,
276            upper: 10,
277        };
278        assert_eq!(v.to_gram_notation(), "1..10");
279
280        let v = Value::Range {
281            lower: -5,
282            upper: 5,
283        };
284        assert_eq!(v.to_gram_notation(), "-5..5");
285    }
286
287    #[test]
288    fn test_tagged_string_serialization() {
289        let v = Value::TaggedString {
290            tag: "markdown".to_string(),
291            content: "# Heading".to_string(),
292        };
293        assert_eq!(v.to_gram_notation(), "\"\"\"markdown# Heading\"\"\"");
294
295        let v = Value::TaggedString {
296            tag: String::new(),
297            content: "Plain text".to_string(),
298        };
299        assert_eq!(v.to_gram_notation(), "\"\"\"Plain text\"\"\"");
300    }
301
302    #[test]
303    fn test_value_type_names() {
304        assert_eq!(Value::String("".to_string()).type_name(), "string");
305        assert_eq!(Value::Integer(0).type_name(), "integer");
306        assert_eq!(Value::Decimal(0.0).type_name(), "decimal");
307        assert_eq!(Value::Boolean(false).type_name(), "boolean");
308        assert_eq!(Value::Array(vec![]).type_name(), "array");
309        assert_eq!(Value::Range { lower: 0, upper: 0 }.type_name(), "range");
310        assert_eq!(
311            Value::TaggedString {
312                tag: String::new(),
313                content: String::new()
314            }
315            .type_name(),
316            "tagged string"
317        );
318    }
319
320    #[test]
321    fn test_value_equality() {
322        assert_eq!(Value::Integer(42), Value::Integer(42));
323        assert_ne!(Value::Integer(42), Value::Integer(43));
324        assert_ne!(Value::Integer(42), Value::String("42".to_string()));
325
326        // Decimal epsilon comparison
327        assert_eq!(Value::Decimal(1.0), Value::Decimal(1.0));
328
329        // Arrays
330        assert_eq!(
331            Value::Array(vec![Value::Integer(1), Value::Integer(2)]),
332            Value::Array(vec![Value::Integer(1), Value::Integer(2)])
333        );
334    }
335
336    #[test]
337    fn test_escape_string() {
338        assert_eq!(escape_string("hello"), "hello");
339        assert_eq!(escape_string("hello\"world"), "hello\\\"world");
340        assert_eq!(escape_string("line1\nline2"), "line1\\nline2");
341        assert_eq!(escape_string("tab\there"), "tab\\there");
342        assert_eq!(escape_string("back\\slash"), "back\\\\slash");
343    }
344
345    #[test]
346    fn test_format_decimal() {
347        assert_eq!(format_decimal(3.14), "3.14");
348        assert_eq!(format_decimal(0.0), "0.0");
349        assert_eq!(format_decimal(1.0), "1.0");
350        assert_eq!(format_decimal(-2.5), "-2.5");
351    }
352}