Skip to main content

semantic/behavior/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Conservative, source-backed Rust conditional-value comparison.
3//!
4//! These are structural facts, not a control-flow/effects model. Source-local
5//! identities include exact blobs and structural occurrence order; matching
6//! across revisions uses symbol/target and normalized tokens, never node IDs.
7
8pub use objects::object::ContentHash;
9use objects::object::{ByteSpan, SymbolEntry};
10
11mod compare;
12mod extract;
13
14pub const EXTRACTOR_VERSION: u32 = 1;
15pub const BINDING_VERSION: u32 = 1;
16pub const COMPARISON_VERSION: u32 = 1;
17pub const MAX_SOURCE_BYTES: usize = 1024 * 1024;
18pub const MAX_ASSIGNMENTS: usize = 512;
19
20pub fn extraction_key(blob: ContentHash) -> ContentHash {
21    extract::hash(&[
22        b"extraction",
23        blob.as_bytes(),
24        crate::semantic_index::grammar_version(crate::parser::Language::Rust).as_bytes(),
25        &crate::semantic_index::EXTRACTOR_VERSION.to_le_bytes(),
26        &EXTRACTOR_VERSION.to_le_bytes(),
27    ])
28}
29
30pub fn binding_key(extraction: ContentHash) -> ContentHash {
31    // Extraction binds the whole source blob, not just a symbol digest.
32    extract::hash(&[
33        b"bindings",
34        extraction.as_bytes(),
35        &BINDING_VERSION.to_le_bytes(),
36    ])
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum Side {
41    Base,
42    Source,
43}
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum Support {
46    Supported,
47    Partial,
48    Unsupported,
49    Unavailable,
50}
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum Limitation {
53    ParseError,
54    Language,
55    MissingElse,
56    Syntax,
57    MutableBinding,
58    UnresolvedBinding,
59    AmbiguousMatch,
60    Budget,
61    MissingSource,
62}
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct Location {
65    pub side: Side,
66    pub span: ByteSpan,
67    pub text: String,
68}
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct Operation {
71    pub id: String,
72    pub symbol: SymbolEntry,
73    pub source: Location,
74}
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum AssignmentKind {
77    FieldInitializer,
78    Assignment,
79}
80#[derive(Clone, Debug, PartialEq, Eq)]
81pub struct Assignment {
82    pub id: String,
83    pub operation_id: String,
84    pub target: String,
85    pub kind: AssignmentKind,
86    pub value_id: String,
87    pub source: Location,
88}
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub enum BranchLabel {
91    True,
92    False,
93}
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct Branch {
96    pub id: String,
97    pub label: BranchLabel,
98    pub result_id: String,
99    pub source: Location,
100}
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct Conditional {
103    pub predicate_id: String,
104    pub branches: Vec<Branch>,
105}
106#[derive(Clone, Debug, PartialEq, Eq)]
107pub struct Expression {
108    pub id: String,
109    pub source: Location,
110    pub normalized_hash: ContentHash,
111    pub conditional: Option<Conditional>,
112}
113#[derive(Clone, Debug, PartialEq, Eq)]
114pub struct Binding {
115    pub id: String,
116    pub occurrence_id: String,
117    pub name: String,
118    pub value_id: Option<String>,
119    pub declaration: Option<Location>,
120    pub lexical_scope: Option<ByteSpan>,
121    pub support: Support,
122    pub limitations: Vec<Limitation>,
123}
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub enum CorrespondenceKind {
126    Retained,
127    Replaced,
128    Added,
129    Removed,
130    Unmatched,
131    Ambiguous,
132}
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
134pub enum MatchReason {
135    SameTargetInMatchedSymbol,
136    ExactNormalizedExpression,
137    ResolvedBinding,
138    SameBranchLabel,
139}
140#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct Correspondence {
142    pub id: String,
143    pub base_ids: Vec<String>,
144    pub source_ids: Vec<String>,
145    pub kind: CorrespondenceKind,
146    pub reasons: Vec<MatchReason>,
147}
148#[derive(Clone, Debug, PartialEq, Eq)]
149pub struct Change {
150    pub id: String,
151    pub symbol_address: String,
152    pub support: Support,
153    pub limitations: Vec<Limitation>,
154    pub operations: Vec<Operation>,
155    pub assignments: Vec<Assignment>,
156    pub expressions: Vec<Expression>,
157    pub bindings: Vec<Binding>,
158    pub correspondences: Vec<Correspondence>,
159}
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub struct Scope {
162    pub symbol_address: String,
163    pub target: String,
164    pub support: Support,
165    pub limitations: Vec<Limitation>,
166    pub sources: Vec<Location>,
167}
168#[derive(Clone, Debug, PartialEq, Eq)]
169pub struct Comparison {
170    pub base_blob: Option<ContentHash>,
171    pub source_blob: Option<ContentHash>,
172    pub changes: Vec<Change>,
173    pub analyzed: Vec<Scope>,
174    pub omitted: Vec<Scope>,
175    pub exhausted: bool,
176}
177
178/// Analyze one exact path pair. `None` means absent source, not an empty file.
179/// Selectors are exact qualified symbol addresses. The host bounds file count
180/// and total bytes; this function also bounds each parser input independently.
181pub fn compare_file(
182    path: &str,
183    base: Option<&str>,
184    source: Option<&str>,
185    symbols: &[String],
186) -> Comparison {
187    let mut comparison = Comparison {
188        base_blob: base.map(|s| ContentHash::compute_typed("blob", s.as_bytes())),
189        source_blob: source.map(|s| ContentHash::compute_typed("blob", s.as_bytes())),
190        changes: vec![],
191        analyzed: vec![],
192        omitted: vec![],
193        exhausted: true,
194    };
195    compare::run(path, base, source, symbols, &mut comparison);
196    comparison
197}
198
199#[cfg(test)]
200mod tests;