1use std::path::PathBuf;
2use std::sync::Arc;
3
4use crate::env::Env;
5use crate::error::RuntimeError;
6use crate::hunk::EditProposal;
7use crate::message::Message;
8use atman_dsl::ast::{Expr, Ident};
9
10#[derive(Debug, Clone)]
11pub enum Value {
12 Unit,
13 Bool(bool),
14 Int(i64),
15 Float(f64),
16 Str(String),
17 Path(PathBuf),
18 List(Vec<Value>),
19 Struct(Vec<(String, Value)>),
20 Message(Message),
21 EditProposal(Box<EditProposal>),
22 Err(RuntimeError),
23 Lambda {
24 params: Vec<Ident>,
25 body: Arc<Expr>,
26 captured_env: Env,
27 },
28}
29
30impl Value {
31 pub fn is_err(&self) -> bool {
32 matches!(self, Value::Err(_))
33 }
34
35 pub fn kind_name(&self) -> &'static str {
36 match self {
37 Value::Unit => "unit",
38 Value::Bool(_) => "bool",
39 Value::Int(_) => "int",
40 Value::Float(_) => "float",
41 Value::Str(_) => "string",
42 Value::Path(_) => "path",
43 Value::List(_) => "list",
44 Value::Struct(_) => "struct",
45 Value::Message(_) => "message",
46 Value::EditProposal(_) => "edit_proposal",
47 Value::Err(_) => "err",
48 Value::Lambda { .. } => "lambda",
49 }
50 }
51
52 pub fn field(&self, name: &str) -> Option<&Value> {
53 if let Value::Struct(fields) = self {
54 fields.iter().find(|(k, _)| k == name).map(|(_, v)| v)
55 } else {
56 None
57 }
58 }
59
60 pub fn to_json(&self) -> serde_json::Value {
61 match self {
62 Value::Unit => serde_json::Value::Null,
63 Value::Bool(b) => serde_json::Value::Bool(*b),
64 Value::Int(i) => serde_json::Value::Number((*i).into()),
65 Value::Float(f) => serde_json::Number::from_f64(*f)
66 .map(serde_json::Value::Number)
67 .unwrap_or(serde_json::Value::Null),
68 Value::Str(s) => serde_json::Value::String(s.clone()),
69 Value::Path(p) => serde_json::Value::String(p.display().to_string()),
70 Value::List(items) => {
71 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
72 }
73 Value::Struct(fields) => {
74 let mut m = serde_json::Map::with_capacity(fields.len());
75 for (k, v) in fields {
76 m.insert(k.clone(), v.to_json());
77 }
78 serde_json::Value::Object(m)
79 }
80 Value::Message(msg) => serde_json::to_value(msg).unwrap_or(serde_json::Value::Null),
81 Value::EditProposal(p) => serde_json::to_value(p).unwrap_or(serde_json::Value::Null),
82 Value::Err(e) => serde_json::json!({ "error": e.to_string() }),
83 Value::Lambda { .. } => serde_json::Value::Null,
84 }
85 }
86
87 pub fn from_json(v: serde_json::Value) -> Self {
88 match v {
89 serde_json::Value::Null => Value::Unit,
90 serde_json::Value::Bool(b) => Value::Bool(b),
91 serde_json::Value::Number(n) => {
92 if let Some(i) = n.as_i64() {
93 Value::Int(i)
94 } else if let Some(f) = n.as_f64() {
95 Value::Float(f)
96 } else {
97 Value::Str(n.to_string())
98 }
99 }
100 serde_json::Value::String(s) => Value::Str(s),
101 serde_json::Value::Array(items) => {
102 Value::List(items.into_iter().map(Value::from_json).collect())
103 }
104 serde_json::Value::Object(map) => Value::Struct(
105 map.into_iter()
106 .map(|(k, v)| (k, Value::from_json(v)))
107 .collect(),
108 ),
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn kind_names_are_stable() {
119 assert_eq!(Value::Unit.kind_name(), "unit");
120 assert_eq!(Value::Bool(true).kind_name(), "bool");
121 assert_eq!(Value::Int(1).kind_name(), "int");
122 assert_eq!(Value::Float(1.0).kind_name(), "float");
123 assert_eq!(Value::Str("x".into()).kind_name(), "string");
124 assert_eq!(Value::Path(PathBuf::from("/tmp")).kind_name(), "path");
125 assert_eq!(Value::List(vec![]).kind_name(), "list");
126 assert_eq!(Value::Struct(vec![]).kind_name(), "struct");
127 assert_eq!(
128 Value::Err(RuntimeError::UndefinedVar("x".into())).kind_name(),
129 "err",
130 );
131 }
132
133 #[test]
134 fn is_err_only_true_for_err_variant() {
135 assert!(!Value::Unit.is_err());
136 assert!(!Value::Bool(false).is_err());
137 assert!(Value::Err(RuntimeError::Cancelled("stop".into())).is_err());
138 }
139
140 #[test]
141 fn struct_field_lookup_returns_by_first_match() {
142 let v = Value::Struct(vec![
143 ("severity".into(), Value::Str("critical".into())),
144 ("count".into(), Value::Int(3)),
145 ]);
146 assert!(matches!(v.field("severity"), Some(Value::Str(s)) if s == "critical"));
147 assert!(matches!(v.field("count"), Some(Value::Int(3))));
148 assert!(v.field("missing").is_none());
149 }
150
151 #[test]
152 fn struct_field_preserves_declaration_order() {
153 let v = Value::Struct(vec![
154 ("a".into(), Value::Int(1)),
155 ("b".into(), Value::Int(2)),
156 ]);
157 if let Value::Struct(fields) = &v {
158 assert_eq!(fields[0].0, "a");
159 assert_eq!(fields[1].0, "b");
160 } else {
161 panic!("expected struct");
162 }
163 }
164
165 #[test]
166 fn runtime_error_display_is_stable() {
167 let msg = RuntimeError::TypeMismatch {
168 expected: "int".into(),
169 actual: "string".into(),
170 }
171 .to_string();
172 assert_eq!(msg, "type mismatch: expected int, got string");
173 }
174}