mos_eval/
semantic_hash.rs1use std::collections::BTreeMap;
4use std::path::PathBuf;
5
6use mos_core::{AttrValue, ContentHasher, Document, NodeKind};
7
8use crate::image_lower::{
9 BITS_PER_COMPONENT_ATTR, COLOR_SPACE_ATTR, PIXEL_HEIGHT_ATTR, PIXEL_WIDTH_ATTR, PIXELS_ATTR,
10};
11use crate::{ExternalDependency, LABEL_SPAN_END_ATTR, LABEL_SPAN_START_ATTR};
12
13pub(crate) fn stamp(document: &mut Document, dependencies: &[ExternalDependency]) {
16 let files: BTreeMap<_, _> = dependencies
17 .iter()
18 .map(|dependency| {
19 (
20 dependency.path.as_path(),
21 dependency
22 .fingerprint
23 .map(|fingerprint| fingerprint.content),
24 )
25 })
26 .collect();
27 document.update_content_hashes(|node| {
28 let mut hasher = ContentHasher::new();
29 hasher
30 .field(b"mos-eval/authored-node/v1")
31 .field(kind_tag(node.kind));
32 for (key, value) in &node.attributes {
36 if matches!(
37 key.as_str(),
38 LABEL_SPAN_START_ATTR | LABEL_SPAN_END_ATTR | "resolved_path"
39 ) || (node.kind == NodeKind::Image
40 && matches!(
41 key.as_str(),
42 PIXELS_ATTR
43 | PIXEL_WIDTH_ATTR
44 | PIXEL_HEIGHT_ATTR
45 | COLOR_SPACE_ATTR
46 | BITS_PER_COMPONENT_ATTR
47 ))
48 || (matches!(
49 node.kind,
50 NodeKind::Reference | NodeKind::PageReference | NodeKind::Citation
51 ) && key == "text")
52 {
53 continue;
54 }
55 hasher.field(b"attr").field(key.as_bytes());
56 hash_value(&mut hasher, value);
57 }
58 if matches!(node.kind, NodeKind::Image | NodeKind::Bibliography) {
59 let path = if node.kind == NodeKind::Bibliography {
60 match node.attributes.get("resolved_path") {
63 Some(AttrValue::Str(path)) => Some(PathBuf::from(path)),
64 _ => None,
65 }
66 } else {
67 match node.attributes.get("src") {
70 Some(AttrValue::Str(src)) => {
71 mos_core::resolve_source_path(src, &node.span.file).ok()
72 }
73 _ => None,
74 }
75 };
76 hasher.field(b"external-input");
77 match path.as_deref().and_then(|path| files.get(path)) {
78 Some(Some(content)) => {
79 hasher.field(b"read").field(&content.0.to_le_bytes());
80 }
81 Some(None) => {
82 hasher.field(b"unreadable");
83 }
84 None => {
85 hasher.field(b"unloaded");
86 }
87 }
88 }
89 hasher.finish()
90 });
91}
92
93fn kind_tag(kind: NodeKind) -> &'static [u8] {
95 match kind {
96 NodeKind::Document => b"document",
97 NodeKind::Section => b"section",
98 NodeKind::Paragraph => b"paragraph",
99 NodeKind::Text => b"text",
100 NodeKind::Emphasis => b"emphasis",
101 NodeKind::Strong => b"strong",
102 NodeKind::BoldItalic => b"bold-italic",
103 NodeKind::Math => b"math",
104 NodeKind::Equation => b"equation",
105 NodeKind::Figure => b"figure",
106 NodeKind::Image => b"image",
107 NodeKind::Table => b"table",
108 NodeKind::Citation => b"citation",
109 NodeKind::Reference => b"reference",
110 NodeKind::PageReference => b"page-reference",
111 NodeKind::Theorem => b"theorem",
112 NodeKind::Footnote => b"footnote",
113 NodeKind::Bibliography => b"bibliography",
114 NodeKind::Raw => b"raw",
115 NodeKind::List => b"list",
116 NodeKind::ListItem => b"list-item",
117 NodeKind::HardBreak => b"hard-break",
118 }
119}
120
121fn hash_value(hasher: &mut ContentHasher, value: &AttrValue) {
122 match value {
123 AttrValue::Bool(value) => {
124 hasher.field(b"bool").u32(u32::from(*value));
125 }
126 AttrValue::Int(value) => {
127 hasher.field(b"int").field(&value.to_le_bytes());
128 }
129 AttrValue::Float(value) => {
130 hasher
131 .field(b"float")
132 .field(&float_bits(*value).to_le_bytes());
133 }
134 AttrValue::Str(value) => {
135 hasher.field(b"str").field(value.as_bytes());
136 }
137 AttrValue::List(values) => {
138 hasher.field(b"list");
139 for value in values {
140 hash_value(hasher, value);
141 }
142 hasher.field(b"end-list");
143 }
144 AttrValue::Length(value) => {
145 hasher
148 .field(b"length")
149 .field(&float_bits((value * 64.0).round()).to_le_bytes());
150 }
151 AttrValue::Bytes(value) => {
152 hasher.field(b"bytes").field(value);
153 }
154 }
155}
156
157fn float_bits(value: f64) -> u64 {
158 if value.is_nan() {
159 0x7ff8_0000_0000_0000
161 } else if value == 0.0 {
162 0
163 } else {
164 value.to_bits()
165 }
166}
167
168#[cfg(test)]
169mod tests;