qanglang_core/frontend/
source.rs1use std::path::{Path, PathBuf};
2
3#[derive(Debug, Clone, Default, PartialEq)]
4pub struct SourceMap {
5 source: Vec<char>,
6 line_indices: Vec<usize>,
7 path: PathBuf,
8}
9
10impl SourceMap {
11 pub fn new(source: String, path: PathBuf) -> Self {
12 let (chars, line_indices) = Self::load_source(source);
13
14 Self {
15 path,
16 source: chars,
17 line_indices,
18 }
19 }
20
21 pub fn from_source(source: String) -> Self {
22 let (chars, line_indices) = Self::load_source(source);
23
24 Self {
25 path: PathBuf::new(),
26 source: chars,
27 line_indices,
28 }
29 }
30
31 pub fn from_path(path: &Path) -> std::io::Result<Self> {
32 let source = std::fs::read_to_string(path)?;
33 let (chars, line_indices) = Self::load_source(source);
34 Ok(Self {
35 path: path.to_path_buf(),
36 line_indices,
37 source: chars,
38 })
39 }
40
41 fn load_source(source: String) -> (Vec<char>, Vec<usize>) {
42 let chars: Vec<char> = source.chars().collect();
43 let mut line_indices = Vec::new();
44 let mut in_string = false;
45 let mut i = 0;
46
47 while i < chars.len() {
48 match chars[i] {
49 '"' if !in_string => {
50 in_string = true;
51 }
52 '"' if in_string => {
53 in_string = false;
54 }
55 '\\' if in_string => {
56 i += 1; if i < chars.len() {
59 i += 1; continue;
61 }
62 }
63 '\n' if !in_string => {
64 line_indices.push(i);
65 }
66 _ => {}
67 }
68 i += 1;
69 }
70
71 (chars, line_indices)
72 }
73
74 pub fn get_path(&self) -> &Path {
75 self.path.as_path()
76 }
77
78 pub fn get_source(&self) -> &Vec<char> {
79 &self.source
80 }
81
82 pub fn get_line(&self, line_number: u32) -> &[char] {
83 if line_number == 0 {
84 return &[];
85 }
86
87 let line_index = (line_number - 1) as usize;
88
89 let start = if line_number == 1 {
90 0
91 } else {
92 if let Some(&prev_newline_pos) = self.line_indices.get(line_index - 1) {
94 prev_newline_pos + 1
95 } else {
96 return &[];
97 }
98 };
99
100 let end = if let Some(&newline_pos) = self.line_indices.get(line_index) {
101 newline_pos } else if line_index == self.line_indices.len() {
103 self.source.len()
105 } else {
106 return &[];
108 };
109
110 if start <= end && end <= self.source.len() {
111 &self.source[start..end]
112 } else {
113 &[]
114 }
115 }
116
117 pub fn get_line_number(&self, position: usize) -> u32 {
118 if position >= self.source.len() {
119 return (self.line_indices.len() + 1) as u32;
120 }
121
122 match self.line_indices.binary_search(&position) {
124 Ok(index) => (index + 1) as u32, Err(index) => (index + 1) as u32, }
127 }
128
129 pub fn get_column_number(&self, position: usize) -> u32 {
130 if position >= self.source.len() {
131 return 1;
132 }
133 let line_number = self.get_line_number(position);
134 let line_start = if line_number == 1 {
135 0
136 } else {
137 let line_index = (line_number - 2) as usize;
138 if let Some(&newline_pos) = self.line_indices.get(line_index) {
139 newline_pos + 1
140 } else {
141 0
142 }
143 };
144 (position - line_start + 1) as u32
145 }
146
147 pub fn position_to_offset(&self, line: u32, column: u32) -> Option<usize> {
149 let target_line = line as usize;
151 let target_col = column as usize;
152
153 let line_start = if target_line == 0 {
155 0
156 } else {
157 if let Some(&newline_pos) = self.line_indices.get(target_line - 1) {
159 newline_pos + 1
160 } else {
161 return None;
163 }
164 };
165
166 let offset = line_start + target_col;
168
169 if offset <= self.source.len() {
171 Some(offset)
172 } else {
173 None
174 }
175 }
176}