textlane_core/models/
location.rs1use crate::Allocator;
2use crate::models::nlcst::Node;
3use crate::models::position::{Point, Position, UnistPoint, UnistPosition};
4
5#[derive(Clone, Debug)]
8pub struct Location<'a> {
9 source: &'a str,
10 starts: Vec<usize>,
11 units: Vec<u32>,
13 bytes: Vec<u32>,
14}
15
16const INSIDE: u32 = u32::MAX;
18
19impl<'a> Location<'a> {
20 pub fn new(source: &'a str) -> Self {
21 let bytes = source.as_bytes();
22 let mut starts = vec![0];
23 for (index, byte) in bytes.iter().enumerate() {
24 match byte {
25 b'\n' => starts.push(index + 1),
26 b'\r' if bytes.get(index + 1) != Some(&b'\n') => starts.push(index + 1),
27 _ => {}
28 }
29 }
30 let mut location = Self {
31 source,
32 starts,
33 units: Vec::new(),
34 bytes: Vec::new(),
35 };
36 if !source.is_ascii() {
37 let mut units = Vec::with_capacity(source.len() + 1);
38 let mut offsets = Vec::with_capacity(source.len() + 1);
39 let mut unit = 0;
40 for (index, character) in source.char_indices() {
41 units.resize(index + character.len_utf8(), unit);
42 offsets.push(index as u32);
43 if character.len_utf16() == 2 {
44 offsets.push(INSIDE);
45 }
46 unit += character.len_utf16() as u32;
47 }
48 units.push(unit);
49 offsets.push(source.len() as u32);
50 location.units = units;
51 location.bytes = offsets;
52 }
53 location
54 }
55
56 pub fn source(&self) -> &'a str {
57 self.source
58 }
59
60 pub fn len(&self) -> usize {
62 self.to_unit(self.source.len())
63 }
64
65 pub fn is_empty(&self) -> bool {
66 self.source.is_empty()
67 }
68
69 pub fn to_unit(&self, byte: usize) -> usize {
71 let byte = byte.min(self.source.len());
72 match self.units.get(byte) {
73 Some(unit) => *unit as usize,
74 None => byte,
75 }
76 }
77
78 pub fn to_byte(&self, unit: usize) -> Option<usize> {
80 if self.units.is_empty() {
81 return (unit <= self.source.len()).then_some(unit);
82 }
83 match self.bytes.get(unit) {
84 Some(&INSIDE) | None => None,
85 Some(byte) => Some(*byte as usize),
86 }
87 }
88
89 pub fn to_unist_point(&self, point: Point) -> UnistPoint {
91 let offset = point.offset().min(self.source.len());
92 let line = self.line(offset);
93 let extra = usize::from(point.virtual_spaces() > 0) + (point.offset() - offset);
94 let unit = self.to_unit(offset);
95 UnistPoint {
96 line,
97 column: unit - self.to_unit(self.starts[line - 1]) + 1 + extra,
98 offset: Some(unit + extra),
99 }
100 }
101
102 pub fn to_point(&self, unit: usize) -> Option<UnistPoint> {
104 if unit > self.len() {
105 return None;
106 }
107 let byte = match self.to_byte(unit) {
108 Some(byte) => byte,
109 None => self.to_byte(unit.checked_sub(1)?)?,
110 };
111 let line = self.line(byte);
112 Some(UnistPoint {
113 line,
114 column: unit - self.to_unit(self.starts[line - 1]) + 1,
115 offset: Some(unit),
116 })
117 }
118
119 pub fn to_offset(&self, line: usize, column: usize) -> Option<usize> {
122 let start = self.to_unit(*self.starts.get(line.checked_sub(1)?)?);
123 let next = match self.starts.get(line) {
124 Some(next) => self.to_unit(*next),
125 None => self.len() + 1,
126 };
127 let offset = (start + column).checked_sub(1)?;
128 (offset < next).then_some(offset)
129 }
130
131 pub fn to_position<'b>(
134 &self,
135 start: usize,
136 end: usize,
137 alloc: &'b Allocator,
138 ) -> Option<Position<'b>> {
139 match (self.to_byte(start), self.to_byte(end)) {
140 (Some(start), Some(end)) => {
141 Some(Position::span(Point::new(start, 0), Point::new(end, 0)))
142 }
143 _ => {
144 let start = self.to_point(start)?;
145 let end = self.to_point(end)?;
146 Some(Position::Unist(alloc.alloc(UnistPosition { start, end })))
147 }
148 }
149 }
150
151 pub fn place_nlcst<'b>(
155 &self,
156 nodes: &mut [Node<'b>],
157 start: Option<usize>,
158 alloc: &'b Allocator,
159 ) -> Option<usize> {
160 let mut from = start;
161 for node in nodes {
162 let to = match node {
163 Node::Root(parent)
164 | Node::Paragraph(parent)
165 | Node::Sentence(parent)
166 | Node::Word(parent) => {
167 let to = self.place_nlcst(&mut parent.children, from, alloc);
169 parent.position = self.place(from, to, alloc);
170 to
171 }
172 Node::Text(literal)
173 | Node::Symbol(literal)
174 | Node::Punctuation(literal)
175 | Node::WhiteSpace(literal)
176 | Node::Source(literal) => {
177 let to = from.map(|from| from + count_units(literal.value));
178 literal.position = self.place(from, to, alloc);
179 to
180 }
181 Node::Unknown(unknown) => {
182 unknown.position = None;
183 from
184 }
185 };
186 from = to;
187 }
188 from
189 }
190
191 pub fn join<'b>(
193 &self,
194 start: &Position<'b>,
195 end: &Position<'b>,
196 alloc: &'b Allocator,
197 ) -> Position<'b> {
198 match (start, end) {
199 (Position::Span { start, .. }, Position::Span { end, .. }) => {
200 Position::span(*start, *end)
201 }
202 _ => Position::Unist(alloc.alloc(UnistPosition {
203 start: self.to_unist_position(start).start,
204 end: self.to_unist_position(end).end,
205 })),
206 }
207 }
208
209 fn to_unist_position(&self, position: &Position<'_>) -> UnistPosition {
210 match position {
211 Position::Span { start, end } => UnistPosition {
212 start: self.to_unist_point(*start),
213 end: self.to_unist_point(*end),
214 },
215 Position::Unist(position) => (*position).clone(),
216 }
217 }
218
219 fn place<'b>(
220 &self,
221 from: Option<usize>,
222 to: Option<usize>,
223 alloc: &'b Allocator,
224 ) -> Option<Position<'b>> {
225 self.to_position(from?, to?, alloc)
226 }
227
228 fn line(&self, byte: usize) -> usize {
230 self.starts.partition_point(|start| *start <= byte)
231 }
232}
233
234fn count_units(value: &str) -> usize {
236 if value.is_ascii() {
237 value.len()
238 } else {
239 value.chars().map(char::len_utf16).sum()
240 }
241}
242
243#[cfg(test)]
244#[path = "location_unit.rs"]
245mod unit;