1use crate::resource_table::{self, PathId, StrId, TokenId};
11use crate::text_table::TextId;
12use serde::de::Error as DeError;
13use serde::ser::Error as SerError;
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15use std::cell::RefCell;
16use std::collections::HashMap;
17use std::path::PathBuf;
18
19#[derive(Clone, Copy, Debug, Default)]
22pub struct IdWindow {
23 pub start: usize,
24 pub end: usize,
25}
26
27impl IdWindow {
28 pub fn count(&self) -> usize {
29 self.end - self.start
30 }
31
32 pub fn encode(&self, id: usize, what: &str) -> Result<u64, String> {
35 if id > self.start && id <= self.end {
36 Ok((id - self.start - 1) as u64)
37 } else {
38 Err(format!(
39 "fragment codec: {what} {id} outside window ({}, {}]",
40 self.start, self.end
41 ))
42 }
43 }
44}
45
46#[derive(Clone, Copy, Debug, Default)]
48pub struct IdRebase {
49 pub base: usize,
50 pub count: usize,
51}
52
53impl IdRebase {
54 pub fn decode(&self, local: u64, what: &str) -> Result<usize, String> {
55 let local = local as usize;
56 if local < self.count {
57 Ok(self.base + local + 1)
58 } else {
59 Err(format!(
60 "fragment codec: {what} local id {local} out of range (count {})",
61 self.count
62 ))
63 }
64 }
65}
66
67#[derive(Default)]
68pub struct EncodeSession {
69 str_map: HashMap<StrId, u32>,
70 str_dict: Vec<String>,
71 path_map: HashMap<PathId, u32>,
72 path_dict: Vec<PathBuf>,
73 token_window: IdWindow,
74 text_window: IdWindow,
75}
76
77impl EncodeSession {
78 pub fn new(token_window: IdWindow, text_window: IdWindow) -> Self {
79 Self {
80 token_window,
81 text_window,
82 ..Default::default()
83 }
84 }
85
86 fn encode_str(&mut self, id: StrId) -> Result<u64, String> {
87 if let Some(x) = self.str_map.get(&id) {
88 return Ok(*x as u64);
89 }
90 let value = resource_table::get_str_value(id)
91 .ok_or_else(|| format!("fragment codec: unknown StrId {}", id.0))?;
92 let local = self.str_dict.len() as u32;
93 self.str_dict.push(value);
94 self.str_map.insert(id, local);
95 Ok(local as u64)
96 }
97
98 fn encode_path(&mut self, id: PathId) -> Result<u64, String> {
99 if let Some(x) = self.path_map.get(&id) {
100 return Ok(*x as u64);
101 }
102 let value = resource_table::get_path_value(id)
103 .ok_or_else(|| format!("fragment codec: unknown PathId {}", id.0))?;
104 let local = self.path_dict.len() as u32;
105 self.path_dict.push(value);
106 self.path_map.insert(id, local);
107 Ok(local as u64)
108 }
109}
110
111pub struct EncodeDicts {
114 pub strings: Vec<String>,
115 pub paths: Vec<PathBuf>,
116}
117
118pub struct DecodeSession {
119 strs: Vec<StrId>,
120 paths: Vec<PathId>,
121 token_rebase: IdRebase,
122 text_rebase: IdRebase,
123}
124
125impl DecodeSession {
126 pub fn new(
129 strings: &[String],
130 paths: &[PathBuf],
131 token_rebase: IdRebase,
132 text_rebase: IdRebase,
133 ) -> Self {
134 let strs = strings
135 .iter()
136 .map(|x| resource_table::insert_str(x))
137 .collect();
138 let paths = paths
139 .iter()
140 .map(|x| resource_table::insert_path(x))
141 .collect();
142 Self {
143 strs,
144 paths,
145 token_rebase,
146 text_rebase,
147 }
148 }
149
150 fn decode_str(&self, local: u64) -> Result<StrId, String> {
151 self.strs
152 .get(local as usize)
153 .copied()
154 .ok_or_else(|| format!("fragment codec: StrId index {local} out of dictionary"))
155 }
156
157 fn decode_path(&self, local: u64) -> Result<PathId, String> {
158 self.paths
159 .get(local as usize)
160 .copied()
161 .ok_or_else(|| format!("fragment codec: PathId index {local} out of dictionary"))
162 }
163}
164
165thread_local!(static ENCODE: RefCell<Option<EncodeSession>> = const { RefCell::new(None) });
166thread_local!(static DECODE: RefCell<Option<DecodeSession>> = const { RefCell::new(None) });
167
168pub fn begin_encode(session: EncodeSession) {
169 ENCODE.with(|f| *f.borrow_mut() = Some(session));
170}
171
172pub fn end_encode() -> Option<EncodeDicts> {
174 ENCODE.with(|f| {
175 f.borrow_mut().take().map(|x| EncodeDicts {
176 strings: x.str_dict,
177 paths: x.path_dict,
178 })
179 })
180}
181
182pub fn begin_decode(session: DecodeSession) {
183 DECODE.with(|f| *f.borrow_mut() = Some(session));
184}
185
186pub fn end_decode() {
187 DECODE.with(|f| *f.borrow_mut() = None);
188}
189
190fn with_encode<R>(f: impl FnOnce(Option<&mut EncodeSession>) -> R) -> R {
191 ENCODE.with(|x| f(x.borrow_mut().as_mut()))
192}
193
194fn with_decode<R>(f: impl FnOnce(Option<&DecodeSession>) -> R) -> R {
195 DECODE.with(|x| f(x.borrow().as_ref()))
196}
197
198impl Serialize for StrId {
199 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
200 let value = with_encode(|session| match session {
201 Some(session) => session.encode_str(*self),
202 None => Ok(self.0 as u64),
203 })
204 .map_err(S::Error::custom)?;
205 serializer.serialize_u64(value)
206 }
207}
208
209impl<'de> Deserialize<'de> for StrId {
210 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
211 let value = u64::deserialize(deserializer)?;
212 with_decode(|session| match session {
213 Some(session) => session.decode_str(value),
214 None => Ok(StrId(value as usize)),
215 })
216 .map_err(D::Error::custom)
217 }
218}
219
220impl Serialize for PathId {
221 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
222 let value = with_encode(|session| match session {
223 Some(session) => session.encode_path(*self),
224 None => Ok(self.0 as u64),
225 })
226 .map_err(S::Error::custom)?;
227 serializer.serialize_u64(value)
228 }
229}
230
231impl<'de> Deserialize<'de> for PathId {
232 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
233 let value = u64::deserialize(deserializer)?;
234 with_decode(|session| match session {
235 Some(session) => session.decode_path(value),
236 None => Ok(PathId(value as usize)),
237 })
238 .map_err(D::Error::custom)
239 }
240}
241
242impl Serialize for TokenId {
243 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
244 let value = with_encode(|session| match session {
245 Some(session) => session.token_window.encode(self.0, "TokenId"),
246 None => Ok(self.0 as u64),
247 })
248 .map_err(S::Error::custom)?;
249 serializer.serialize_u64(value)
250 }
251}
252
253impl<'de> Deserialize<'de> for TokenId {
254 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
255 let value = u64::deserialize(deserializer)?;
256 with_decode(|session| match session {
257 Some(session) => session.token_rebase.decode(value, "TokenId").map(TokenId),
258 None => Ok(TokenId(value as usize)),
259 })
260 .map_err(D::Error::custom)
261 }
262}
263
264impl Serialize for TextId {
265 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
266 let value = with_encode(|session| match session {
267 Some(session) => session.text_window.encode(self.0, "TextId"),
268 None => Ok(self.0 as u64),
269 })
270 .map_err(S::Error::custom)?;
271 serializer.serialize_u64(value)
272 }
273}
274
275impl<'de> Deserialize<'de> for TextId {
276 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
277 let value = u64::deserialize(deserializer)?;
278 with_decode(|session| match session {
279 Some(session) => session.text_rebase.decode(value, "TextId").map(TextId),
280 None => Ok(TextId(value as usize)),
281 })
282 .map_err(D::Error::custom)
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::text_table::{self, TextInfo};
290 use crate::veryl_token::{Token, TokenSource};
291 use std::path::Path;
292
293 #[test]
294 fn token_roundtrip_rebases_ids() {
295 let token_start = resource_table::peek_token_id();
296 let text_start = text_table::peek_text_id();
297
298 let path = resource_table::insert_path(Path::new("roundtrip.veryl"));
299 let text = text_table::set_current_text(TextInfo {
300 text: "module A {}".to_string(),
301 path,
302 });
303 let source = TokenSource::File { path, text };
304 let a = Token::new("alpha", 1, 2, 5, 0, source);
305 let b = Token::new("beta", 2, 3, 4, 10, source);
306
307 let token_end = resource_table::peek_token_id();
308 let text_end = text_table::peek_text_id();
309 let token_count = token_end - token_start;
310 let text_count = text_end - text_start;
311
312 begin_encode(EncodeSession::new(
313 IdWindow {
314 start: token_start,
315 end: token_end,
316 },
317 IdWindow {
318 start: text_start,
319 end: text_end,
320 },
321 ));
322 let bytes = postcard::to_allocvec(&(a, b)).unwrap();
323 let dicts = end_encode().unwrap();
324 assert!(dicts.strings.contains(&"alpha".to_string()));
325 assert!(
326 dicts
327 .paths
328 .contains(&Path::new("roundtrip.veryl").to_path_buf())
329 );
330
331 let token_base = resource_table::reserve_token_ids(token_count);
332 let text_base = text_table::reserve_text_ids(text_count);
333 begin_decode(DecodeSession::new(
334 &dicts.strings,
335 &dicts.paths,
336 IdRebase {
337 base: token_base,
338 count: token_count,
339 },
340 IdRebase {
341 base: text_base,
342 count: text_count,
343 },
344 ));
345 let (a2, b2): (Token, Token) = postcard::from_bytes(&bytes).unwrap();
346 end_decode();
347
348 assert_eq!(a2.id.0, token_base + (a.id.0 - token_start));
350 assert_eq!(b2.id.0, token_base + (b.id.0 - token_start));
351 assert!(a2.id < b2.id);
352 assert_eq!(a2.text, resource_table::insert_str("alpha"));
354 assert_eq!(b2.text, resource_table::insert_str("beta"));
355 assert_eq!((a2.line, a2.column, a2.length, a2.pos), (1, 2, 5, 0));
356 match a2.source {
357 TokenSource::File { path, text } => {
358 assert_eq!(
359 resource_table::get_path_value(path).unwrap(),
360 Path::new("roundtrip.veryl").to_path_buf()
361 );
362 assert_eq!(text.0, text_base + 1);
363 }
364 _ => panic!("source kind changed"),
365 }
366 }
367
368 #[test]
369 fn out_of_window_token_fails_encode() {
370 let outside = Token::new("x", 0, 0, 1, 0, TokenSource::External);
371 let start = resource_table::peek_token_id();
372 let _inside = Token::new("y", 0, 0, 1, 0, TokenSource::External);
373 let end = resource_table::peek_token_id();
374
375 begin_encode(EncodeSession::new(
376 IdWindow { start, end },
377 IdWindow::default(),
378 ));
379 let result = postcard::to_allocvec(&outside);
380 end_encode();
381 assert!(result.is_err());
382 }
383
384 #[test]
385 fn passthrough_without_session() {
386 let bytes = postcard::to_allocvec(&TokenId(42)).unwrap();
387 let id: TokenId = postcard::from_bytes(&bytes).unwrap();
388 assert_eq!(id, TokenId(42));
389 }
390}