Skip to main content

atman_runtime/
value.rs

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}