Skip to main content

shape_runtime/
annotation_context.rs

1//! Annotation context and decorator registry
2//!
3//! This module provides the infrastructure for Shape's annotation system.
4//!
5//! ## Design Philosophy
6//!
7//! Annotations in Shape are **fully defined in Shape stdlib**, not hardcoded in Rust.
8//! This module provides the generic runtime primitives that annotation lifecycle hooks use.
9//!
10//! ## Annotation Lifecycle Hooks
11//!
12//! Annotations can define handlers for different lifecycle events:
13//! - `on_define(fn, ctx)` - Called when function is first defined
14//! - `before(fn, args, ctx)` - Called before each function invocation
15//! - `after(fn, args, result, ctx)` - Called after each function invocation
16//! - `metadata()` - Static metadata for tooling and optimization
17//!
18//! ## Example (stdlib/finance/annotations/pattern.shape)
19//!
20//! ```shape
21//! annotation pattern() {
22//!     on_define(fn, ctx) {
23//!         ctx.registry("patterns").set(fn.name, fn);
24//!     }
25//!     metadata() { return { is_pattern: true }; }
26//! }
27//! ```
28//!
29//! ## Runtime Primitives
30//!
31//! The `AnnotationContext` provides domain-agnostic primitives:
32//! - `cache` - Key-value cache for memoization
33//! - `state` - Per-annotation persistent state
34//! - `registry(name)` - Named registries (patterns, strategies, features, etc.)
35//! - `emit(event, data)` - Event emission for alerts, logging
36//! - `data` - Data range manipulation (extend/restore for warmup)
37
38use shape_value::ValueWord;
39use std::collections::HashMap;
40
41// ============================================================================
42// Annotation Registry
43// ============================================================================
44
45/// Registry for annotation definitions
46///
47/// Stores `annotation ... { ... }` definitions that can be looked up by name.
48/// These definitions include lifecycle hooks (on_define, before, after, metadata).
49#[derive(Clone)]
50pub struct AnnotationRegistry {
51    annotations: HashMap<String, shape_ast::ast::AnnotationDef>,
52}
53
54impl AnnotationRegistry {
55    pub fn new() -> Self {
56        Self {
57            annotations: HashMap::new(),
58        }
59    }
60
61    pub fn register(&mut self, def: shape_ast::ast::AnnotationDef) {
62        self.annotations.insert(def.name.clone(), def);
63    }
64
65    pub fn get(&self, name: &str) -> Option<&shape_ast::ast::AnnotationDef> {
66        self.annotations.get(name)
67    }
68
69    pub fn contains(&self, name: &str) -> bool {
70        self.annotations.contains_key(name)
71    }
72}
73
74impl Default for AnnotationRegistry {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80// ============================================================================
81// Annotation Context - Runtime Primitives
82// ============================================================================
83
84/// Context passed to annotation lifecycle hooks
85///
86/// Provides domain-agnostic primitives that annotation handlers use.
87/// This is the `ctx` parameter in handlers like `on_define(fn, ctx)`.
88#[derive(Debug, Clone)]
89pub struct AnnotationContext {
90    /// Key-value cache for memoization (e.g., @cached, @indicator)
91    cache: AnnotationCache,
92    /// Per-annotation persistent state
93    state: AnnotationState,
94    /// Named registries (patterns, strategies, features, etc.)
95    registries: HashMap<String, NamedRegistry>,
96    /// Emitted events (for @alert, @logged annotations)
97    events: Vec<EmittedEvent>,
98    /// Data range manipulation state (for @warmup)
99    data_range: DataRangeState,
100}
101
102impl AnnotationContext {
103    pub fn new() -> Self {
104        Self {
105            cache: AnnotationCache::new(),
106            state: AnnotationState::new(),
107            registries: HashMap::new(),
108            events: Vec::new(),
109            data_range: DataRangeState::new(),
110        }
111    }
112
113    /// Get the cache for memoization
114    pub fn cache(&self) -> &AnnotationCache {
115        &self.cache
116    }
117
118    /// Get mutable cache for memoization
119    pub fn cache_mut(&mut self) -> &mut AnnotationCache {
120        &mut self.cache
121    }
122
123    /// Get the per-annotation state
124    pub fn state(&self) -> &AnnotationState {
125        &self.state
126    }
127
128    /// Get mutable per-annotation state
129    pub fn state_mut(&mut self) -> &mut AnnotationState {
130        &mut self.state
131    }
132
133    /// Get or create a named registry
134    pub fn registry(&mut self, name: &str) -> &mut NamedRegistry {
135        self.registries.entry(name.to_string()).or_default()
136    }
137
138    /// Emit an event (for alerts, logging, etc.)
139    pub fn emit(&mut self, event_type: &str, data: ValueWord) {
140        self.events.push(EmittedEvent {
141            event_type: event_type.to_string(),
142            data,
143            timestamp: std::time::Instant::now(),
144        });
145    }
146
147    /// Get all emitted events
148    pub fn events(&self) -> &[EmittedEvent] {
149        &self.events
150    }
151
152    /// Clear emitted events
153    pub fn clear_events(&mut self) {
154        self.events.clear();
155    }
156
157    /// Get data range manipulation state
158    pub fn data_range(&self) -> &DataRangeState {
159        &self.data_range
160    }
161
162    /// Get mutable data range manipulation state
163    pub fn data_range_mut(&mut self) -> &mut DataRangeState {
164        &mut self.data_range
165    }
166}
167
168impl Default for AnnotationContext {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174// ============================================================================
175// Cache Primitive
176// ============================================================================
177
178/// Key-value cache for annotation memoization
179///
180/// Used by annotations like @cached, @indicator, @memo
181#[derive(Debug, Clone, Default)]
182pub struct AnnotationCache {
183    entries: HashMap<String, CacheEntry>,
184}
185
186#[derive(Debug, Clone)]
187pub struct CacheEntry {
188    pub value: ValueWord,
189    pub created_at: std::time::Instant,
190}
191
192impl AnnotationCache {
193    pub fn new() -> Self {
194        Self {
195            entries: HashMap::new(),
196        }
197    }
198
199    /// Get a cached value by key as ValueWord reference
200    pub fn get(&self, key: &str) -> Option<&ValueWord> {
201        self.entries.get(key).map(|e| &e.value)
202    }
203
204    /// Get a cached entry (includes metadata)
205    pub fn get_entry(&self, key: &str) -> Option<&CacheEntry> {
206        self.entries.get(key)
207    }
208
209    /// Set a cached value
210    pub fn set(&mut self, key: String, value: ValueWord) {
211        self.entries.insert(
212            key,
213            CacheEntry {
214                value,
215                created_at: std::time::Instant::now(),
216            },
217        );
218    }
219
220    /// Check if key exists
221    pub fn contains(&self, key: &str) -> bool {
222        self.entries.contains_key(key)
223    }
224
225    /// Remove a cached value
226    pub fn remove(&mut self, key: &str) -> Option<ValueWord> {
227        self.entries.remove(key).map(|e| e.value)
228    }
229
230    /// Clear all cached values
231    pub fn clear(&mut self) {
232        self.entries.clear();
233    }
234}
235
236// ============================================================================
237// State Primitive
238// ============================================================================
239
240/// Per-annotation persistent state
241///
242/// Used for annotations that need to maintain state across calls
243#[derive(Debug, Clone, Default)]
244pub struct AnnotationState {
245    values: HashMap<String, ValueWord>,
246}
247
248impl AnnotationState {
249    pub fn new() -> Self {
250        Self {
251            values: HashMap::new(),
252        }
253    }
254
255    /// Get a state value as ValueWord reference
256    pub fn get(&self, key: &str) -> Option<&ValueWord> {
257        self.values.get(key)
258    }
259
260    pub fn set(&mut self, key: String, value: ValueWord) {
261        self.values.insert(key, value);
262    }
263
264    pub fn contains(&self, key: &str) -> bool {
265        self.values.contains_key(key)
266    }
267
268    pub fn remove(&mut self, key: &str) -> Option<ValueWord> {
269        self.values.remove(key)
270    }
271
272    pub fn clear(&mut self) {
273        self.values.clear();
274    }
275}
276
277// ============================================================================
278// Registry Primitive
279// ============================================================================
280
281/// A named registry for storing values (functions, patterns, etc.)
282///
283/// Used by custom annotations (e.g. @strategy, @feature)
284#[derive(Debug, Clone, Default)]
285pub struct NamedRegistry {
286    entries: HashMap<String, ValueWord>,
287}
288
289impl NamedRegistry {
290    pub fn new() -> Self {
291        Self {
292            entries: HashMap::new(),
293        }
294    }
295
296    /// Get a registry value as ValueWord reference
297    pub fn get(&self, key: &str) -> Option<&ValueWord> {
298        self.entries.get(key)
299    }
300
301    pub fn set(&mut self, key: String, value: ValueWord) {
302        self.entries.insert(key, value);
303    }
304
305    pub fn contains(&self, key: &str) -> bool {
306        self.entries.contains_key(key)
307    }
308
309    pub fn remove(&mut self, key: &str) -> Option<ValueWord> {
310        self.entries.remove(key)
311    }
312
313    pub fn keys(&self) -> impl Iterator<Item = &String> {
314        self.entries.keys()
315    }
316
317    /// Iterate over values as ValueWord references
318    pub fn values(&self) -> impl Iterator<Item = &ValueWord> {
319        self.entries.values()
320    }
321
322    pub fn len(&self) -> usize {
323        self.entries.len()
324    }
325
326    pub fn is_empty(&self) -> bool {
327        self.entries.is_empty()
328    }
329}
330
331// ============================================================================
332// Event Emission Primitive
333// ============================================================================
334
335/// An emitted event from an annotation handler
336///
337/// Used by annotations like @alert, @logged, @audit_logged
338#[derive(Debug, Clone)]
339pub struct EmittedEvent {
340    pub event_type: String,
341    pub data: ValueWord,
342    pub timestamp: std::time::Instant,
343}
344
345// ============================================================================
346// Data Range Manipulation
347// ============================================================================
348
349/// State for data range manipulation (used by @warmup)
350///
351/// Allows annotations to extend the data range (e.g., for warmup periods)
352/// and then restore it after processing.
353#[derive(Debug, Clone, Default)]
354pub struct DataRangeState {
355    /// Original data range start (if extended)
356    original_start: Option<usize>,
357    /// Original data range end (if extended)
358    original_end: Option<usize>,
359    /// Amount the range was extended
360    extension_amount: Option<usize>,
361}
362
363impl DataRangeState {
364    pub fn new() -> Self {
365        Self {
366            original_start: None,
367            original_end: None,
368            extension_amount: None,
369        }
370    }
371
372    /// Record the original range before extending
373    pub fn save_original(&mut self, start: usize, end: usize) {
374        self.original_start = Some(start);
375        self.original_end = Some(end);
376    }
377
378    /// Record how much the range was extended
379    pub fn set_extension(&mut self, amount: usize) {
380        self.extension_amount = Some(amount);
381    }
382
383    /// Get the original start position
384    pub fn original_start(&self) -> Option<usize> {
385        self.original_start
386    }
387
388    /// Get the original end position
389    pub fn original_end(&self) -> Option<usize> {
390        self.original_end
391    }
392
393    /// Get the extension amount
394    pub fn extension_amount(&self) -> Option<usize> {
395        self.extension_amount
396    }
397
398    /// Check if range is currently extended
399    pub fn is_extended(&self) -> bool {
400        self.extension_amount.is_some()
401    }
402
403    /// Clear the saved state (after restoring)
404    pub fn clear(&mut self) {
405        self.original_start = None;
406        self.original_end = None;
407        self.extension_amount = None;
408    }
409}
410
411// ============================================================================
412// Annotation Processor
413// ============================================================================