Skip to main content

triblespace_core/value/schemas/
linelocation.rs

1use crate::blob::schemas::longstring::LongString;
2use crate::id::ExclusiveId;
3use crate::id::Id;
4use crate::id_hex;
5use crate::macros::entity;
6use crate::metadata;
7use crate::metadata::ConstMetadata;
8use crate::repo::BlobStore;
9use crate::trible::TribleSet;
10use crate::value::schemas::hash::Blake3;
11use crate::value::FromValue;
12use crate::value::RawValue;
13use crate::value::ToValue;
14use crate::value::Value;
15use crate::value::ValueSchema;
16use proc_macro::Span;
17use std::convert::Infallible;
18
19#[cfg(feature = "wasm")]
20use crate::blob::schemas::wasmcode::WasmCode;
21/// A value schema for representing a span using explicit line and column
22/// coordinates.
23#[derive(Debug, Clone, Copy)]
24pub struct LineLocation;
25
26impl ConstMetadata for LineLocation {
27    fn id() -> Id {
28        id_hex!("DFAED173A908498CB893A076EAD3E578")
29    }
30
31    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
32    where
33        B: BlobStore<Blake3>,
34    {
35        let id = Self::id();
36        let description = blobs.put::<LongString, _>(
37            "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.",
38        )?;
39        let tribles = entity! {
40            ExclusiveId::force_ref(&id) @
41                metadata::shortname: "line_location",
42                metadata::description: description,
43                metadata::tag: metadata::KIND_VALUE_SCHEMA,
44        };
45
46        #[cfg(feature = "wasm")]
47        let tribles = {
48            let mut tribles = tribles;
49            tribles += entity! { ExclusiveId::force_ref(&id) @
50                metadata::value_formatter: blobs.put::<WasmCode, _>(wasm_formatter::LINELOCATION_WASM)?,
51            };
52            tribles
53        };
54        Ok(tribles)
55    }
56}
57
58#[cfg(feature = "wasm")]
59mod wasm_formatter {
60    use core::fmt::Write;
61
62    use triblespace_core_macros::value_formatter;
63
64    #[value_formatter]
65    pub(crate) fn linelocation(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
66        let mut buf = [0u8; 8];
67        buf.copy_from_slice(&raw[..8]);
68        let start_line = u64::from_be_bytes(buf);
69        buf.copy_from_slice(&raw[8..16]);
70        let start_col = u64::from_be_bytes(buf);
71        buf.copy_from_slice(&raw[16..24]);
72        let end_line = u64::from_be_bytes(buf);
73        buf.copy_from_slice(&raw[24..]);
74        let end_col = u64::from_be_bytes(buf);
75
76        write!(out, "{start_line}:{start_col}..{end_line}:{end_col}").map_err(|_| 1u32)?;
77        Ok(())
78    }
79}
80
81impl ValueSchema for LineLocation {
82    type ValidationError = Infallible;
83}
84
85fn encode_location(lines: (u64, u64, u64, u64)) -> RawValue {
86    let mut raw = [0u8; 32];
87    raw[..8].copy_from_slice(&lines.0.to_be_bytes());
88    raw[8..16].copy_from_slice(&lines.1.to_be_bytes());
89    raw[16..24].copy_from_slice(&lines.2.to_be_bytes());
90    raw[24..].copy_from_slice(&lines.3.to_be_bytes());
91    raw
92}
93
94fn decode_location(raw: &RawValue) -> (u64, u64, u64, u64) {
95    let mut first = [0u8; 8];
96    let mut second = [0u8; 8];
97    let mut third = [0u8; 8];
98    let mut fourth = [0u8; 8];
99    first.copy_from_slice(&raw[..8]);
100    second.copy_from_slice(&raw[8..16]);
101    third.copy_from_slice(&raw[16..24]);
102    fourth.copy_from_slice(&raw[24..]);
103    (
104        u64::from_be_bytes(first),
105        u64::from_be_bytes(second),
106        u64::from_be_bytes(third),
107        u64::from_be_bytes(fourth),
108    )
109}
110
111impl ToValue<LineLocation> for (u64, u64, u64, u64) {
112    fn to_value(self) -> Value<LineLocation> {
113        Value::new(encode_location(self))
114    }
115}
116
117impl FromValue<'_, LineLocation> for (u64, u64, u64, u64) {
118    fn from_value(v: &Value<LineLocation>) -> Self {
119        decode_location(&v.raw)
120    }
121}
122
123impl ToValue<LineLocation> for Span {
124    fn to_value(self) -> Value<LineLocation> {
125        (
126            self.start().line() as u64,
127            self.start().column() as u64,
128            self.end().line() as u64,
129            self.end().column() as u64,
130        )
131            .to_value()
132    }
133}