Skip to main content

triblespace_core/inline/encodings/
linelocation.rs

1use crate::inline::Encodes;
2use crate::id::ExclusiveId;
3use crate::id::Id;
4use crate::id_hex;
5use crate::macros::entity;
6use crate::metadata;
7use crate::metadata::MetaDescribe;
8use crate::trible::Fragment;
9use crate::inline::RawInline;
10use crate::inline::IntoInline;
11use crate::inline::TryFromInline;
12use crate::inline::Inline;
13use crate::inline::InlineEncoding;
14use proc_macro::Span;
15use std::convert::Infallible;
16
17/// A inline encoding for representing a span using explicit line and column
18/// coordinates.
19#[derive(Debug, Clone, Copy)]
20pub struct LineLocation;
21
22impl MetaDescribe for LineLocation {
23    fn describe() -> Fragment {
24        let id: Id = id_hex!("DFAED173A908498CB893A076EAD3E578");
25        #[allow(unused_mut)]
26        let mut tribles = entity! {
27            ExclusiveId::force_ref(&id) @
28                metadata::name: "line_location",
29                metadata::description: "Line/column span encoded as four big-endian u64 values (start_line, start_col, end_line, end_col). This captures explicit source positions rather than byte offsets.\n\nUse for editor diagnostics, source maps, or human-facing spans. If you need byte offsets or length-based ranges, use RangeU128 instead.\n\nColumns are raw counts and do not account for variable-width graphemes. Store any display conventions separately if needed.",
30                metadata::tag: metadata::KIND_INLINE_ENCODING,
31        };
32
33        #[cfg(feature = "wasm")]
34        {
35            tribles += entity! { ExclusiveId::force_ref(&id) @
36                metadata::value_formatter: wasm_formatter::LINELOCATION_WASM,
37            };
38        }
39        tribles
40    }
41}
42
43#[cfg(feature = "wasm")]
44mod wasm_formatter {
45    use core::fmt::Write;
46
47    use triblespace_core_macros::value_formatter;
48
49    #[value_formatter]
50    pub(crate) fn linelocation(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
51        let mut buf = [0u8; 8];
52        buf.copy_from_slice(&raw[..8]);
53        let start_line = u64::from_be_bytes(buf);
54        buf.copy_from_slice(&raw[8..16]);
55        let start_col = u64::from_be_bytes(buf);
56        buf.copy_from_slice(&raw[16..24]);
57        let end_line = u64::from_be_bytes(buf);
58        buf.copy_from_slice(&raw[24..]);
59        let end_col = u64::from_be_bytes(buf);
60
61        write!(out, "{start_line}:{start_col}..{end_line}:{end_col}").map_err(|_| 1u32)?;
62        Ok(())
63    }
64}
65
66impl InlineEncoding for LineLocation {
67    type ValidationError = Infallible;
68    type Encoding = Self;
69}
70
71fn encode_location(lines: (u64, u64, u64, u64)) -> RawInline {
72    let mut raw = [0u8; 32];
73    raw[..8].copy_from_slice(&lines.0.to_be_bytes());
74    raw[8..16].copy_from_slice(&lines.1.to_be_bytes());
75    raw[16..24].copy_from_slice(&lines.2.to_be_bytes());
76    raw[24..].copy_from_slice(&lines.3.to_be_bytes());
77    raw
78}
79
80fn decode_location(raw: &RawInline) -> (u64, u64, u64, u64) {
81    let mut first = [0u8; 8];
82    let mut second = [0u8; 8];
83    let mut third = [0u8; 8];
84    let mut fourth = [0u8; 8];
85    first.copy_from_slice(&raw[..8]);
86    second.copy_from_slice(&raw[8..16]);
87    third.copy_from_slice(&raw[16..24]);
88    fourth.copy_from_slice(&raw[24..]);
89    (
90        u64::from_be_bytes(first),
91        u64::from_be_bytes(second),
92        u64::from_be_bytes(third),
93        u64::from_be_bytes(fourth),
94    )
95}
96
97impl Encodes<(u64, u64, u64, u64)> for LineLocation
98{
99    type Output = Inline<LineLocation>;
100    fn encode(source: (u64, u64, u64, u64)) -> Inline<LineLocation> {
101        Inline::new(encode_location(source))
102    }
103}
104
105impl TryFromInline<'_, LineLocation> for (u64, u64, u64, u64) {
106    type Error = Infallible;
107    fn try_from_inline(v: &Inline<LineLocation>) -> Result<Self, Infallible> {
108        Ok(decode_location(&v.raw))
109    }
110}
111
112impl Encodes<Span> for LineLocation
113{
114    type Output = Inline<LineLocation>;
115    fn encode(source: Span) -> Inline<LineLocation> {
116        (
117            source.start().line() as u64,
118            source.start().column() as u64,
119            source.end().line() as u64,
120            source.end().column() as u64,
121        )
122            .to_inline()
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::inline::IntoInline;
130    use proptest::prelude::*;
131
132    proptest! {
133        #[test]
134        fn tuple_roundtrip(a: u64, b: u64, c: u64, d: u64) {
135            let input = (a, b, c, d);
136            let value: Inline<LineLocation> = input.to_inline();
137            let output: (u64, u64, u64, u64) = value.from_inline();
138            prop_assert_eq!(input, output);
139        }
140
141        #[test]
142        fn validates(a: u64, b: u64, c: u64, d: u64) {
143            let value: Inline<LineLocation> = (a, b, c, d).to_inline();
144            prop_assert!(LineLocation::validate(value).is_ok());
145        }
146    }
147}