aces/
content.rs

1use std::{
2    ops::{AddAssign, MulAssign},
3    collections::{BTreeMap, BTreeSet},
4    path::Path,
5    fmt,
6    error::Error,
7};
8use crate::{NodeID, ContextHandle};
9
10pub trait ContentFormat: fmt::Debug {
11    // Note: this can't be declared as an associated const due to
12    // object safety constraints.
13    fn expected_extensions(&self) -> &[&str];
14
15    fn path_is_acceptable(&self, path: &Path) -> bool {
16        if let Some(ext) = path.extension() {
17            if !ext.is_empty() {
18                if let Some(ext) = ext.to_str() {
19                    for expected in self.expected_extensions() {
20                        if ext.eq_ignore_ascii_case(expected) {
21                            return true
22                        }
23                    }
24                }
25
26                // Reject unexpected nonempty extension, including any
27                // extension that isn't a valid UTF8.
28                return false
29            }
30        }
31
32        // Accept empty extension.
33        true
34    }
35
36    fn script_is_acceptable(&self, script: &str) -> bool;
37
38    fn script_to_content(
39        &self,
40        cxt: &ContextHandle,
41        script: &str,
42    ) -> Result<Box<dyn Content>, Box<dyn Error>>;
43}
44
45#[derive(Clone, Default, Debug)]
46pub struct InteractiveFormat;
47
48impl InteractiveFormat {
49    pub fn new() -> Self {
50        Default::default()
51    }
52}
53
54impl ContentFormat for InteractiveFormat {
55    fn expected_extensions(&self) -> &[&str] {
56        &[]
57    }
58
59    fn script_is_acceptable(&self, _script: &str) -> bool {
60        false
61    }
62
63    fn script_to_content(
64        &self,
65        _cxt: &ContextHandle,
66        _script: &str,
67    ) -> Result<Box<dyn Content>, Box<dyn Error>> {
68        panic!("Attempt to bypass a script acceptance test.")
69    }
70}
71
72// FIXME define specific iterators for return types below
73/// An abstraction over script formats: various ways c-e structures
74/// are described in text.
75///
76/// This trait is implemented by intermediate representation types,
77/// [`YamlContent`] and `ascesis::*`.
78///
79/// Note: types implementing `Content` trait shouldn't own
80/// [`ContextHandle`]s, because `Content` trait objects are owned by
81/// [`CEStructure`] structs, along with [`ContextHandle`]s themselves.
82///
83/// [`CEStructure`]: crate::CEStructure
84/// [`YamlContent`]: crate::yaml_script::YamlContent
85pub trait Content: fmt::Debug {
86    /// `Script` is a content description in text, for example,
87    /// YAML-formatted string or _Ascesis_ source.
88    fn get_script(&self) -> Option<&str>;
89    fn get_name(&self) -> Option<&str>;
90    fn get_carrier_ids(&mut self) -> Vec<NodeID>;
91    fn get_causes_by_id(&self, id: NodeID) -> Option<&Vec<Vec<NodeID>>>;
92    fn get_effects_by_id(&self, id: NodeID) -> Option<&Vec<Vec<NodeID>>>;
93}
94
95impl Content for String {
96    fn get_script(&self) -> Option<&str> {
97        Some(self)
98    }
99
100    fn get_name(&self) -> Option<&str> {
101        panic!("Attempt to access a phantom content.")
102    }
103
104    fn get_carrier_ids(&mut self) -> Vec<NodeID> {
105        panic!("Attempt to access a phantom content.")
106    }
107
108    fn get_causes_by_id(&self, _id: NodeID) -> Option<&Vec<Vec<NodeID>>> {
109        panic!("Attempt to access a phantom content.")
110    }
111
112    fn get_effects_by_id(&self, _id: NodeID) -> Option<&Vec<Vec<NodeID>>> {
113        panic!("Attempt to access a phantom content.")
114    }
115}
116
117impl<'a, C: Content + 'a> From<C> for Box<dyn Content + 'a> {
118    fn from(content: C) -> Box<dyn Content + 'a> {
119        Box::new(content)
120    }
121}
122
123#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Default, Debug)]
124pub(crate) struct MonoForContent {
125    content: Vec<NodeID>,
126}
127
128impl MonoForContent {
129    pub(crate) fn new() -> Self {
130        Default::default()
131    }
132
133    pub(crate) fn add_node(&mut self, id: NodeID) {
134        if let Err(pos) = self.content.binary_search(&id) {
135            self.content.insert(pos, id);
136        }
137    }
138
139    pub(crate) fn into_content(self) -> Vec<NodeID> {
140        self.content
141    }
142}
143
144#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Default, Debug)]
145pub(crate) struct PolyForContent {
146    content: Vec<Vec<NodeID>>,
147}
148
149impl PolyForContent {
150    pub(crate) fn new() -> Self {
151        Default::default()
152    }
153
154    pub(crate) fn add_mono(&mut self, mono: Vec<NodeID>) {
155        if let Err(pos) = self.content.binary_search(&mono) {
156            self.content.insert(pos, mono);
157        }
158    }
159
160    pub(crate) fn multiply_by_mono(&mut self, mono: Vec<NodeID>) {
161        if self.content.is_empty() {
162            self.add_mono(mono);
163        } else {
164            let mut old_poly = Vec::new();
165            old_poly.append(&mut self.content);
166
167            for mut old_mono in old_poly {
168                for node_id in mono.iter() {
169                    if let Err(pos) = old_mono.binary_search(node_id) {
170                        old_mono.insert(pos, *node_id);
171                    }
172                }
173                self.add_mono(old_mono);
174            }
175        }
176    }
177
178    pub(crate) fn as_content(&self) -> &Vec<Vec<NodeID>> {
179        &self.content
180    }
181}
182
183impl AddAssign for PolyForContent {
184    fn add_assign(&mut self, other: Self) {
185        for mono in other.content {
186            self.add_mono(mono);
187        }
188    }
189}
190
191#[derive(Clone, Default, Debug)]
192struct MapForContent {
193    content: BTreeMap<NodeID, PolyForContent>,
194}
195
196impl MapForContent {
197    // `poly.is_empty()` test is ommited here, because it is done by the
198    // only callers, `PartialContent::add_to_causes()` and
199    // `PartialContent::add_to_effects()`.
200    fn add_poly(&mut self, id: NodeID, poly: &[Vec<NodeID>]) {
201        self.content
202            .entry(id)
203            .and_modify(|old_poly| {
204                for mono in poly {
205                    old_poly.add_mono(mono.to_vec());
206                }
207            })
208            .or_insert_with(|| PolyForContent { content: poly.to_vec() });
209    }
210
211    // `poly.is_empty()` test is ommited here, because it is done by the
212    // only callers, `PartialContent::multiply_causes()` and
213    // `PartialContent::multiply_effects()`.
214    fn multiply_by_poly(&mut self, id: NodeID, poly: &[Vec<NodeID>]) {
215        self.content
216            .entry(id)
217            .and_modify(|old_poly| {
218                let mut new_poly = PolyForContent::new();
219                for mono in poly {
220                    let mut aux_poly = old_poly.clone();
221                    aux_poly.multiply_by_mono(mono.to_vec());
222                    new_poly += aux_poly;
223                }
224                *old_poly = new_poly;
225            })
226            .or_insert_with(|| PolyForContent { content: poly.to_vec() });
227    }
228}
229
230#[derive(Clone, Default, Debug)]
231struct CarrierForContent {
232    content: Option<BTreeSet<NodeID>>,
233}
234
235impl CarrierForContent {
236    fn touch(&mut self, id: NodeID) {
237        if let Some(ref content) = self.content {
238            if content.contains(&id) {
239                return
240            }
241        }
242        self.content = None;
243    }
244
245    fn maybe_update(&mut self, causes: &MapForContent, effects: &MapForContent) {
246        if self.content.is_none() {
247            self.content =
248                Some(effects.content.keys().chain(causes.content.keys()).copied().collect());
249        }
250    }
251
252    fn as_owned_content(&self) -> Vec<NodeID> {
253        self.content.as_ref().unwrap().iter().copied().collect()
254    }
255}
256
257#[derive(Clone, Debug)]
258pub struct PartialContent {
259    context: ContextHandle,
260    carrier: CarrierForContent,
261    causes:  MapForContent,
262    effects: MapForContent,
263}
264
265impl PartialContent {
266    pub fn new(ctx: &ContextHandle) -> Self {
267        PartialContent {
268            context: ctx.clone(),
269            carrier: Default::default(),
270            causes:  Default::default(),
271            effects: Default::default(),
272        }
273    }
274
275    pub fn get_context(&self) -> &ContextHandle {
276        &self.context
277    }
278
279    pub fn add_to_causes(&mut self, id: NodeID, poly: &[Vec<NodeID>]) {
280        if !poly.is_empty() {
281            self.causes.add_poly(id, poly);
282            self.carrier.touch(id);
283        }
284    }
285
286    pub fn add_to_effects(&mut self, id: NodeID, poly: &[Vec<NodeID>]) {
287        if !poly.is_empty() {
288            self.effects.add_poly(id, poly);
289            self.carrier.touch(id);
290        }
291    }
292
293    pub fn multiply_causes(&mut self, id: NodeID, poly: &[Vec<NodeID>]) {
294        if !poly.is_empty() {
295            self.causes.multiply_by_poly(id, poly);
296            self.carrier.touch(id);
297        }
298    }
299
300    pub fn multiply_effects(&mut self, id: NodeID, poly: &[Vec<NodeID>]) {
301        if !poly.is_empty() {
302            self.effects.multiply_by_poly(id, poly);
303            self.carrier.touch(id);
304        }
305    }
306}
307
308impl AddAssign for PartialContent {
309    fn add_assign(&mut self, other: Self) {
310        for (id, poly) in other.causes.content {
311            self.add_to_causes(id, &poly.content);
312        }
313
314        for (id, poly) in other.effects.content {
315            self.add_to_effects(id, &poly.content);
316        }
317    }
318}
319
320impl MulAssign for PartialContent {
321    fn mul_assign(&mut self, other: Self) {
322        for (id, poly) in other.causes.content {
323            self.multiply_causes(id, &poly.content);
324        }
325
326        for (id, poly) in other.effects.content {
327            self.multiply_effects(id, &poly.content);
328        }
329    }
330}
331
332impl Content for PartialContent {
333    fn get_script(&self) -> Option<&str> {
334        None
335    }
336
337    fn get_name(&self) -> Option<&str> {
338        None
339    }
340
341    fn get_carrier_ids(&mut self) -> Vec<NodeID> {
342        self.carrier.maybe_update(&self.causes, &self.effects);
343        self.carrier.as_owned_content()
344    }
345
346    fn get_causes_by_id(&self, id: NodeID) -> Option<&Vec<Vec<NodeID>>> {
347        self.causes.content.get(&id).map(|poly| poly.as_content())
348    }
349
350    fn get_effects_by_id(&self, id: NodeID) -> Option<&Vec<Vec<NodeID>>> {
351        self.effects.content.get(&id).map(|poly| poly.as_content())
352    }
353}
354
355pub trait CompilableMut {
356    fn compile_mut(&mut self, ctx: &ContextHandle) -> Result<bool, Box<dyn Error>>;
357}
358
359pub trait Compilable {
360    fn compile(&self, ctx: &ContextHandle) -> Result<bool, Box<dyn Error>>;
361}
362
363pub trait CompilableAsContent {
364    /// Get a compiled [`PartialContent`] of `self`.
365    ///
366    /// Expected to return a content previously stored in the given
367    /// [`Context`], if possible, or to compile `self` and return the
368    /// result.  Expected to return error if not all dependencies of
369    /// `self` are retrievable from the given [`Context`] as a
370    /// [`PartialContent`].
371    ///
372    /// Note: unlike [`compile_as_dependency()`], this function isn't
373    /// expected to store compilation result in the [`Context`].
374    ///
375    /// [`Context`]: crate::Context
376    /// [`compile_as_dependency()`]: CompilableAsDependency::compile_as_dependency()
377    fn get_compiled_content(&self, ctx: &ContextHandle) -> Result<PartialContent, Box<dyn Error>>;
378
379    /// Check whether all dependencies of `self` are retrievable from
380    /// the given [`Context`].
381    ///
382    /// Expected to return `None` if none of dependencies is missing
383    /// from [`Context`], or a name of a missing dependency, chosen
384    /// arbitrarily.
385    ///
386    /// [`Context`]: crate::Context
387    fn check_dependencies(&self, _ctx: &ContextHandle) -> Option<String> {
388        None
389    }
390}
391
392pub trait CompilableAsDependency: CompilableAsContent {
393    /// Compile `self` and store the result in the given [`Context`].
394    ///
395    /// Expected to return `None` if none of dependencies is missing
396    /// from [`Context`], or a name of a missing dependency, chosen
397    /// arbitrarily.
398    ///
399    /// [`Context`]: crate::Context
400    fn compile_as_dependency(&self, ctx: &ContextHandle) -> Result<Option<String>, Box<dyn Error>>;
401}