Skip to main content

pepl_codegen/
source_map.rs

1//! Source mapping — WASM function index → PEPL source location.
2//!
3//! Each entry maps a compiled WASM function to its originating PEPL source
4//! span (line, column).  This enables the host to resolve WASM traps back to
5//! human-readable source positions.
6//!
7//! Granularity is per-function in Phase 0.  Future phases may add
8//! instruction-level mappings.
9
10use serde::{Deserialize, Serialize};
11
12/// A complete source map for a compiled PEPL module.
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14pub struct SourceMap {
15    pub entries: Vec<SourceMapEntry>,
16}
17
18/// A single source map entry: one WASM function → one PEPL source region.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct SourceMapEntry {
21    /// Absolute WASM function index (imports + runtime + space functions).
22    pub wasm_func_index: u32,
23    /// Human-readable name of the function (e.g. "init", "dispatch_action",
24    /// "increment", "__test_0").
25    pub func_name: String,
26    /// Kind of function for host grouping.
27    pub kind: FuncKind,
28    /// Source span (1-based line/column).
29    pub span: pepl_types::Span,
30}
31
32/// Classification of a compiled function for the host.
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34#[serde(rename_all = "snake_case")]
35pub enum FuncKind {
36    /// Space-level infrastructure (init, dispatch, render, get_state, dealloc).
37    SpaceInfra,
38    /// An action implementation.
39    Action,
40    /// A view render function.
41    View,
42    /// The update(dt) loop callback.
43    Update,
44    /// The handleEvent callback.
45    HandleEvent,
46    /// A compiled test case (__test_N).
47    Test,
48    /// The __test_count helper.
49    TestCount,
50    /// A compiled lambda body.
51    Lambda,
52    /// invoke_lambda trampoline.
53    InvokeLambda,
54}
55
56impl SourceMap {
57    pub fn new() -> Self {
58        Self {
59            entries: Vec::new(),
60        }
61    }
62
63    /// Push a new entry.
64    pub fn push(
65        &mut self,
66        wasm_func_index: u32,
67        func_name: impl Into<String>,
68        kind: FuncKind,
69        span: pepl_types::Span,
70    ) {
71        self.entries.push(SourceMapEntry {
72            wasm_func_index,
73            func_name: func_name.into(),
74            kind,
75            span,
76        });
77    }
78
79    /// Look up the entry whose WASM function index matches.
80    pub fn find_by_func_index(&self, idx: u32) -> Option<&SourceMapEntry> {
81        self.entries.iter().find(|e| e.wasm_func_index == idx)
82    }
83
84    /// Serialize to JSON bytes for embedding in a WASM custom section.
85    pub fn to_json(&self) -> Vec<u8> {
86        serde_json::to_vec(self).unwrap_or_default()
87    }
88
89    /// Deserialize from JSON bytes.
90    pub fn from_json(data: &[u8]) -> Option<Self> {
91        serde_json::from_slice(data).ok()
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use pepl_types::Span;
99
100    #[test]
101    fn round_trip_json() {
102        let mut sm = SourceMap::new();
103        sm.push(35, "init", FuncKind::SpaceInfra, Span::new(5, 3, 10, 4));
104        sm.push(36, "dispatch_action", FuncKind::SpaceInfra, Span::new(12, 3, 30, 4));
105        sm.push(37, "increment", FuncKind::Action, Span::new(15, 5, 18, 6));
106
107        let json = sm.to_json();
108        let sm2 = SourceMap::from_json(&json).expect("parse failed");
109        assert_eq!(sm2.entries.len(), 3);
110        assert_eq!(sm2.entries[0].func_name, "init");
111        assert_eq!(sm2.entries[2].kind, FuncKind::Action);
112    }
113
114    #[test]
115    fn find_by_func_index() {
116        let mut sm = SourceMap::new();
117        sm.push(35, "init", FuncKind::SpaceInfra, Span::new(5, 3, 10, 4));
118        sm.push(36, "dispatch", FuncKind::SpaceInfra, Span::new(12, 3, 30, 4));
119
120        assert!(sm.find_by_func_index(35).is_some());
121        assert_eq!(sm.find_by_func_index(35).unwrap().func_name, "init");
122        assert!(sm.find_by_func_index(99).is_none());
123    }
124}