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// ============================================================================