pipa/compiler/
location.rs1use std::fmt;
2
3#[derive(Debug, Clone, Default)]
4pub struct SourceLocation {
5 pub filename: String,
6 pub line: u32,
7 pub column: u32,
8}
9
10impl SourceLocation {
11 pub fn new(filename: impl Into<String>, line: u32, column: u32) -> Self {
12 Self {
13 filename: filename.into(),
14 line,
15 column,
16 }
17 }
18
19 pub fn unknown() -> Self {
20 Self {
21 filename: "<anonymous>".to_string(),
22 line: 0,
23 column: 0,
24 }
25 }
26}
27
28impl fmt::Display for SourceLocation {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "{}:{}:{}", self.filename, self.line, self.column)
31 }
32}
33
34pub type LineNumberEntry = (u32, u32);
35
36#[derive(Debug, Clone, Default)]
37pub struct LineNumberTable {
38 entries: Vec<LineNumberEntry>,
39}
40
41impl LineNumberTable {
42 pub fn new() -> Self {
43 Self {
44 entries: Vec::new(),
45 }
46 }
47
48 pub fn add_entry(&mut self, instruction_offset: u32, line_number: u32) {
49 if let Some(pos) = self
50 .entries
51 .binary_search_by(|(off, _)| off.cmp(&instruction_offset))
52 .ok()
53 {
54 self.entries[pos] = (instruction_offset, line_number);
55 } else {
56 let pos = self
57 .entries
58 .partition_point(|(off, _)| *off < instruction_offset);
59 self.entries.insert(pos, (instruction_offset, line_number));
60 }
61 }
62
63 pub fn lookup_line(&self, instruction_offset: u32) -> Option<u32> {
64 match self
65 .entries
66 .binary_search_by(|(off, _)| off.cmp(&instruction_offset))
67 {
68 Ok(idx) => Some(self.entries[idx].1),
69 Err(idx) if idx > 0 => Some(self.entries[idx - 1].1),
70 _ => None,
71 }
72 }
73
74 pub fn entries(&self) -> &[LineNumberEntry] {
75 &self.entries
76 }
77}
78
79#[derive(Debug, Clone)]
80pub struct FrameInfo {
81 pub function_name: String,
82 pub location: SourceLocation,
83}
84
85impl FrameInfo {
86 pub fn new(function_name: impl Into<String>, location: SourceLocation) -> Self {
87 Self {
88 function_name: function_name.into(),
89 location,
90 }
91 }
92}
93
94pub struct TracebackFormatter;
95
96impl TracebackFormatter {
97 pub fn format(error_message: &str, frames: &[FrameInfo], error_type: Option<&str>) -> String {
98 let mut result = String::new();
99
100 if let Some(etype) = error_type {
101 result.push_str(&format!("Uncaught {}: {}", etype, error_message));
102 } else {
103 result.push_str(&format!("Uncaught: {}", error_message));
104 }
105 result.push('\n');
106
107 for frame in frames.iter().rev() {
108 result.push_str(&format!(
109 " at {} ({}:{}:{})\n",
110 frame.function_name,
111 frame.location.filename,
112 frame.location.line,
113 frame.location.column
114 ));
115 }
116
117 result
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_line_number_table() {
127 let mut table = LineNumberTable::new();
128
129 table.add_entry(0, 1);
130 table.add_entry(10, 2);
131 table.add_entry(25, 5);
132
133 assert_eq!(table.lookup_line(0), Some(1));
134 assert_eq!(table.lookup_line(5), Some(1));
135 assert_eq!(table.lookup_line(10), Some(2));
136 assert_eq!(table.lookup_line(20), Some(2));
137 assert_eq!(table.lookup_line(25), Some(5));
138 assert_eq!(table.lookup_line(100), Some(5));
139 }
140
141 #[test]
142 fn test_traceback_formatter() {
143 let frames = vec![
144 FrameInfo::new("inner", SourceLocation::new("test.js", 3, 5)),
145 FrameInfo::new("outer", SourceLocation::new("test.js", 7, 2)),
146 FrameInfo::new("global", SourceLocation::new("test.js", 10, 1)),
147 ];
148
149 let output = TracebackFormatter::format("x is not a function", &frames, Some("TypeError"));
150
151 assert!(output.contains("Uncaught TypeError: x is not a function"));
152 assert!(output.contains("at inner (test.js:3:5)"));
153 assert!(output.contains("at outer (test.js:7:2)"));
154 assert!(output.contains("at global (test.js:10:1)"));
155 }
156}