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::KindedSlot;
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: KindedSlot) {
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: KindedSlot,
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 KindedSlot reference
200    pub fn get(&self, key: &str) -> Option<&KindedSlot> {
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: KindedSlot) {
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<KindedSlot> {
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    /// `ValueMap` ensures each per-annotation persistent value's heap ref
246    /// is released when the state is cleared or dropped.
247    values: HashMap<String, KindedSlot>,
248}
249
250impl AnnotationState {
251    pub fn new() -> Self {
252        Self {
253            values: HashMap::new(),
254        }
255    }
256
257    /// Get a state value as KindedSlot reference
258    pub fn get(&self, key: &str) -> Option<&KindedSlot> {
259        self.values.get(key)
260    }
261
262    pub fn set(&mut self, key: String, value: KindedSlot) {
263        self.values.insert(key, value);
264    }
265
266    pub fn contains(&self, key: &str) -> bool {
267        self.values.contains_key(key)
268    }
269
270    pub fn remove(&mut self, key: &str) -> Option<KindedSlot> {
271        self.values.remove(key)
272    }
273
274    pub fn clear(&mut self) {
275        self.values.clear();
276    }
277}
278
279// ============================================================================
280// Registry Primitive
281// ============================================================================
282
283/// A named registry for storing values (functions, patterns, etc.)
284///
285/// Used by custom annotations (e.g. @strategy, @feature)
286#[derive(Debug, Clone, Default)]
287pub struct NamedRegistry {
288    /// `ValueMap` releases each registered heap ref when the registry is
289    /// dropped or a key is overwritten.
290    entries: HashMap<String, KindedSlot>,
291}
292
293impl NamedRegistry {
294    pub fn new() -> Self {
295        Self {
296            entries: HashMap::new(),
297        }
298    }
299
300    /// Get a registry value as KindedSlot reference
301    pub fn get(&self, key: &str) -> Option<&KindedSlot> {
302        self.entries.get(key)
303    }
304
305    pub fn set(&mut self, key: String, value: KindedSlot) {
306        self.entries.insert(key, value);
307    }
308
309    pub fn contains(&self, key: &str) -> bool {
310        self.entries.contains_key(key)
311    }
312
313    pub fn remove(&mut self, key: &str) -> Option<KindedSlot> {
314        self.entries.remove(key)
315    }
316
317    pub fn keys(&self) -> impl Iterator<Item = &String> {
318        self.entries.keys()
319    }
320
321    /// Iterate over values as KindedSlot references
322    pub fn values(&self) -> impl Iterator<Item = &KindedSlot> {
323        self.entries.values()
324    }
325
326    pub fn len(&self) -> usize {
327        self.entries.len()
328    }
329
330    pub fn is_empty(&self) -> bool {
331        self.entries.is_empty()
332    }
333}
334
335// ============================================================================
336// Event Emission Primitive
337// ============================================================================
338
339/// An emitted event from an annotation handler
340///
341/// Used by annotations like @alert, @logged, @audit_logged
342#[derive(Debug, Clone)]
343pub struct EmittedEvent {
344    pub event_type: String,
345    pub data: KindedSlot,
346    pub timestamp: std::time::Instant,
347}
348
349// ============================================================================
350// Data Range Manipulation
351// ============================================================================
352
353/// State for data range manipulation (used by @warmup)
354///
355/// Allows annotations to extend the data range (e.g., for warmup periods)
356/// and then restore it after processing.
357#[derive(Debug, Clone, Default)]
358pub struct DataRangeState {
359    /// Original data range start (if extended)
360    original_start: Option<usize>,
361    /// Original data range end (if extended)
362    original_end: Option<usize>,
363    /// Amount the range was extended
364    extension_amount: Option<usize>,
365}
366
367impl DataRangeState {
368    pub fn new() -> Self {
369        Self {
370            original_start: None,
371            original_end: None,
372            extension_amount: None,
373        }
374    }
375
376    /// Record the original range before extending
377    pub fn save_original(&mut self, start: usize, end: usize) {
378        self.original_start = Some(start);
379        self.original_end = Some(end);
380    }
381
382    /// Record how much the range was extended
383    pub fn set_extension(&mut self, amount: usize) {
384        self.extension_amount = Some(amount);
385    }
386
387    /// Get the original start position
388    pub fn original_start(&self) -> Option<usize> {
389        self.original_start
390    }
391
392    /// Get the original end position
393    pub fn original_end(&self) -> Option<usize> {
394        self.original_end
395    }
396
397    /// Get the extension amount
398    pub fn extension_amount(&self) -> Option<usize> {
399        self.extension_amount
400    }
401
402    /// Check if range is currently extended
403    pub fn is_extended(&self) -> bool {
404        self.extension_amount.is_some()
405    }
406
407    /// Clear the saved state (after restoring)
408    pub fn clear(&mut self) {
409        self.original_start = None;
410        self.original_end = None;
411        self.extension_amount = None;
412    }
413}
414
415// ============================================================================
416// Annotation Processor
417// ============================================================================