meta_language/
substitution.rs1use std::collections::BTreeMap;
2
3use crate::link_network::{Link, LinkId};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct SubstitutionRule {
8 pattern: Vec<LinkId>,
9 replacement: Vec<LinkId>,
10}
11
12impl SubstitutionRule {
13 #[must_use]
16 pub fn new<const P: usize, const R: usize>(
17 pattern: [LinkId; P],
18 replacement: [LinkId; R],
19 ) -> Self {
20 Self {
21 pattern: pattern.to_vec(),
22 replacement: replacement.to_vec(),
23 }
24 }
25
26 #[must_use]
28 pub fn create<const R: usize>(replacement: [LinkId; R]) -> Self {
29 Self {
30 pattern: Vec::new(),
31 replacement: replacement.to_vec(),
32 }
33 }
34
35 #[must_use]
37 pub fn delete<const P: usize>(pattern: [LinkId; P]) -> Self {
38 Self {
39 pattern: pattern.to_vec(),
40 replacement: Vec::new(),
41 }
42 }
43
44 pub(crate) fn pattern(&self) -> &[LinkId] {
45 &self.pattern
46 }
47
48 pub(crate) fn replacement(&self) -> &[LinkId] {
49 &self.replacement
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum SubstitutionValue {
56 Link(LinkId),
58 Variable(String),
60}
61
62impl SubstitutionValue {
63 #[must_use]
65 pub const fn link(link_id: LinkId) -> Self {
66 Self::Link(link_id)
67 }
68
69 #[must_use]
71 pub fn variable(name: impl Into<String>) -> Self {
72 Self::Variable(normalize_variable_name(name))
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct VariableSubstitutionRule {
79 index_variable: Option<String>,
80 pattern: Vec<SubstitutionValue>,
81 replacement: Vec<SubstitutionValue>,
82}
83
84impl VariableSubstitutionRule {
85 #[must_use]
87 pub fn new<const P: usize, const R: usize>(
88 pattern: [SubstitutionValue; P],
89 replacement: [SubstitutionValue; R],
90 ) -> Self {
91 Self {
92 index_variable: None,
93 pattern: Vec::from(pattern),
94 replacement: Vec::from(replacement),
95 }
96 }
97
98 #[must_use]
100 pub fn with_index_variable(mut self, name: impl Into<String>) -> Self {
101 self.index_variable = Some(normalize_variable_name(name));
102 self
103 }
104
105 pub(crate) fn index_variable(&self) -> Option<&str> {
106 self.index_variable.as_deref()
107 }
108
109 pub(crate) fn pattern(&self) -> &[SubstitutionValue] {
110 &self.pattern
111 }
112
113 pub(crate) fn replacement(&self) -> &[SubstitutionValue] {
114 &self.replacement
115 }
116
117 pub(crate) fn match_link(&self, link: &Link) -> Option<SubstitutionBindings> {
118 if link.references().len() != self.pattern.len() {
119 return None;
120 }
121
122 let mut bindings = SubstitutionBindings::default();
123 if let Some(index_variable) = self.index_variable() {
124 bindings.bind(index_variable, link.id())?;
125 }
126
127 for (pattern, reference) in self.pattern.iter().zip(link.references()) {
128 match pattern {
129 SubstitutionValue::Link(expected) if expected == reference => {}
130 SubstitutionValue::Link(_) => return None,
131 SubstitutionValue::Variable(name) => {
132 bindings.bind(name, *reference)?;
133 }
134 }
135 }
136
137 Some(bindings)
138 }
139}
140
141#[derive(Clone, Debug, Default, PartialEq, Eq)]
143pub struct SubstitutionBindings {
144 values: BTreeMap<String, LinkId>,
145}
146
147impl SubstitutionBindings {
148 pub(crate) fn bind(&mut self, name: &str, link_id: LinkId) -> Option<()> {
149 match self.values.get(name) {
150 Some(existing) if *existing != link_id => None,
151 Some(_) => Some(()),
152 None => {
153 self.values.insert(normalize_variable_name(name), link_id);
154 Some(())
155 }
156 }
157 }
158
159 #[must_use]
161 pub fn get(&self, name: &str) -> Option<LinkId> {
162 self.values.get(&normalize_variable_name(name)).copied()
163 }
164
165 pub fn iter(&self) -> impl Iterator<Item = (&str, LinkId)> {
167 self.values
168 .iter()
169 .map(|(name, link_id)| (name.as_str(), *link_id))
170 }
171
172 pub(crate) fn resolve_values(&self, values: &[SubstitutionValue]) -> Option<Vec<LinkId>> {
173 values
174 .iter()
175 .map(|value| match value {
176 SubstitutionValue::Link(link_id) => Some(*link_id),
177 SubstitutionValue::Variable(name) => self.get(name),
178 })
179 .collect()
180 }
181}
182
183#[derive(Clone, Debug, Default, PartialEq, Eq)]
185pub struct SubstitutionReport {
186 pub(crate) created: Vec<LinkId>,
187 pub(crate) updated: Vec<LinkId>,
188 pub(crate) deleted: Vec<LinkId>,
189 pub(crate) bindings: Vec<SubstitutionBindings>,
190}
191
192impl SubstitutionReport {
193 #[must_use]
195 pub fn created(&self) -> &[LinkId] {
196 &self.created
197 }
198
199 #[must_use]
201 pub fn updated(&self) -> &[LinkId] {
202 &self.updated
203 }
204
205 #[must_use]
207 pub fn deleted(&self) -> &[LinkId] {
208 &self.deleted
209 }
210
211 #[must_use]
213 pub fn bindings(&self) -> &[SubstitutionBindings] {
214 &self.bindings
215 }
216}
217
218fn normalize_variable_name(name: impl Into<String>) -> String {
219 name.into().trim_start_matches('$').to_string()
220}