joule_ast/
energy.rs

1//! Energy budget AST representation for Joule
2//!
3//! This module defines the AST structures for energy budget attributes.
4//! Energy budgets allow developers to specify constraints on function energy consumption.
5
6use joule_common::Span;
7
8/// Energy budget constraints for a function or block.
9///
10/// These constraints can be attached to functions via the `#[energy_budget(...)]` attribute
11/// to specify limits on energy consumption, power usage, and temperature impact.
12///
13/// # Example
14/// ```joule
15/// #[energy_budget(max_joules = 0.5)]
16/// fn process(data: &[f32]) -> f32 {
17///     // Compiler can warn if estimated energy > 0.5J
18/// }
19///
20/// #[energy_budget(max_joules = 1.0, max_temp_delta = 5.0)]
21/// fn intensive_compute() {
22///     // Also track temperature increase
23/// }
24/// ```
25#[derive(Debug, Clone, PartialEq)]
26pub struct EnergyBudget {
27    /// Maximum energy consumption in joules
28    pub max_joules: Option<f64>,
29    /// Maximum power consumption in watts
30    pub max_watts: Option<f64>,
31    /// Maximum temperature delta in Celsius
32    pub max_temp_delta: Option<f64>,
33    /// Source span for error reporting
34    pub span: Span,
35}
36
37impl EnergyBudget {
38    /// Create a new empty energy budget
39    pub const fn new(span: Span) -> Self {
40        Self {
41            max_joules: None,
42            max_watts: None,
43            max_temp_delta: None,
44            span,
45        }
46    }
47
48    /// Create an energy budget with `max_joules` constraint
49    pub const fn with_max_joules(joules: f64, span: Span) -> Self {
50        Self {
51            max_joules: Some(joules),
52            max_watts: None,
53            max_temp_delta: None,
54            span,
55        }
56    }
57
58    /// Check if this energy budget has any constraints
59    pub const fn has_constraints(&self) -> bool {
60        self.max_joules.is_some() || self.max_watts.is_some() || self.max_temp_delta.is_some()
61    }
62
63    /// Set `max_joules` constraint
64    pub const fn set_max_joules(&mut self, joules: f64) {
65        self.max_joules = Some(joules);
66    }
67
68    /// Set `max_watts` constraint
69    pub const fn set_max_watts(&mut self, watts: f64) {
70        self.max_watts = Some(watts);
71    }
72
73    /// Set `max_temp_delta` constraint
74    pub const fn set_max_temp_delta(&mut self, delta: f64) {
75        self.max_temp_delta = Some(delta);
76    }
77}
78
79impl Default for EnergyBudget {
80    fn default() -> Self {
81        Self {
82            max_joules: None,
83            max_watts: None,
84            max_temp_delta: None,
85            span: Span::dummy(),
86        }
87    }
88}
89
90/// Represents an attribute attached to an item.
91///
92/// Joule supports Rust-style attributes like `#[energy_budget(max_joules = 0.5)]`.
93#[derive(Debug, Clone, PartialEq)]
94pub struct Attribute {
95    /// The name/path of the attribute (e.g., "`energy_budget`")
96    pub name: crate::Path,
97    /// The arguments to the attribute
98    pub args: AttributeArgs,
99    /// Source span
100    pub span: Span,
101}
102
103/// Arguments to an attribute
104#[derive(Debug, Clone, PartialEq, Default)]
105pub enum AttributeArgs {
106    /// No arguments: `#[attr]`
107    #[default]
108    Empty,
109    /// Parenthesized arguments: `#[attr(key = value, ...)]`
110    Parenthesized(Vec<AttributeArg>),
111}
112
113/// A single argument in an attribute
114#[derive(Debug, Clone, PartialEq)]
115pub struct AttributeArg {
116    /// The key name (e.g., "`max_joules`")
117    pub key: crate::Ident,
118    /// The value (e.g., 0.5)
119    pub value: AttributeValue,
120    /// Source span
121    pub span: Span,
122}
123
124/// Value types allowed in attributes
125#[derive(Debug, Clone, PartialEq)]
126pub enum AttributeValue {
127    /// A literal float: `0.5`
128    Float(f64),
129    /// A literal integer: `100`
130    Int(u64),
131    /// A string literal: `"hello"`
132    String(String),
133    /// A boolean: `true` or `false`
134    Bool(bool),
135    /// An identifier: `SomeVariant`
136    Ident(crate::Ident),
137}
138
139impl AttributeValue {
140    /// Try to get the value as f64
141    pub const fn as_f64(&self) -> Option<f64> {
142        match self {
143            Self::Float(f) => Some(*f),
144            Self::Int(i) => Some(*i as f64),
145            _ => None,
146        }
147    }
148
149    /// Try to get the value as u64
150    pub const fn as_u64(&self) -> Option<u64> {
151        match self {
152            Self::Int(i) => Some(*i),
153            _ => None,
154        }
155    }
156
157    /// Try to get the value as bool
158    pub const fn as_bool(&self) -> Option<bool> {
159        match self {
160            Self::Bool(b) => Some(*b),
161            _ => None,
162        }
163    }
164
165    /// Try to get the value as string
166    pub fn as_string(&self) -> Option<&str> {
167        match self {
168            Self::String(s) => Some(s),
169            _ => None,
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use joule_common::Symbol;
178
179    fn make_ident(name: &str) -> crate::Ident {
180        crate::Ident::new(Symbol::intern(name), Span::dummy())
181    }
182
183    #[test]
184    fn test_energy_budget_default() {
185        let budget = EnergyBudget::default();
186        assert!(budget.max_joules.is_none());
187        assert!(budget.max_watts.is_none());
188        assert!(budget.max_temp_delta.is_none());
189        assert!(!budget.has_constraints());
190    }
191
192    #[test]
193    fn test_energy_budget_with_constraints() {
194        let budget = EnergyBudget::with_max_joules(0.5, Span::dummy());
195        assert_eq!(budget.max_joules, Some(0.5));
196        assert!(budget.has_constraints());
197    }
198
199    #[test]
200    fn test_energy_budget_setters() {
201        let mut budget = EnergyBudget::new(Span::dummy());
202        budget.set_max_joules(1.0);
203        budget.set_max_watts(50.0);
204        budget.set_max_temp_delta(10.0);
205
206        assert_eq!(budget.max_joules, Some(1.0));
207        assert_eq!(budget.max_watts, Some(50.0));
208        assert_eq!(budget.max_temp_delta, Some(10.0));
209    }
210
211    #[test]
212    fn test_attribute_value_conversions() {
213        let float_val = AttributeValue::Float(0.5);
214        assert_eq!(float_val.as_f64(), Some(0.5));
215
216        let int_val = AttributeValue::Int(100);
217        assert_eq!(int_val.as_u64(), Some(100));
218        assert_eq!(int_val.as_f64(), Some(100.0)); // ints can be floats
219
220        let bool_val = AttributeValue::Bool(true);
221        assert_eq!(bool_val.as_bool(), Some(true));
222
223        let string_val = AttributeValue::String("test".to_string());
224        assert_eq!(string_val.as_string(), Some("test"));
225
226        let ident_val = AttributeValue::Ident(make_ident("SomeValue"));
227        assert!(ident_val.as_f64().is_none());
228    }
229
230    #[test]
231    fn test_attribute_args_default() {
232        let args = AttributeArgs::default();
233        assert!(matches!(args, AttributeArgs::Empty));
234    }
235}