hexroll3_scroll/
semantics.rs

1/*
2// Copyright (C) 2020-2025 Pen, Dice & Paper
3//
4// This program is dual-licensed under the following terms:
5//
6// Option 1: (Non-Commercial) GNU Affero General Public License (AGPL)
7// This program is free software: you can redistribute it and/or modify
8// it under the terms of the GNU Affero General Public License as
9// published by the Free Software Foundation, either version 3 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15// GNU Affero General Public License for more details.
16//
17// You should have received a copy of the GNU Affero General Public License
18// along with this program. If not, see <http://www.gnu.org/licenses/>.
19//
20// Option 2: Commercial License
21// For commercial use, you are required to obtain a separate commercial
22// license. Please contact ithai at pendicepaper.com
23// for more information about commercial licensing terms.
24*/
25use std::mem::take;
26use std::sync::Arc;
27
28use anyhow::Result;
29use indexmap::IndexMap;
30use std::marker::Send;
31use std::marker::Sync;
32
33use crate::{instance::*, repository::*};
34
35/// Scroll class definition data:
36///
37/// ```text
38/// ClassName(ParentClassName) {
39///   attribute = value
40///   ...
41/// }
42/// ```
43/// Classes are used to generate random entities.
44#[derive(Clone)]
45pub struct Class {
46    pub name: String,
47    pub attrs: IndexMap<String, Attr>,
48    pub subclasses: SubclassesSpecifier,
49    pub hierarchy: Vec<String>,
50    pub collects: Vec<CollectionSpecifier>,
51    pub html_body: Option<String>,
52    pub html_header: Option<String>,
53}
54
55/// Provides the class subclasses for instantiation, either using a List:
56/// ```text
57/// Class {
58///   ^ [
59///       * Subclass1
60///       * Subclass2
61///     ]
62/// }
63/// ```
64///  or using a variable:
65/// ```text
66/// list_of_subclasses = [
67///   * Subclass1
68///   * Subclass2
69/// ]
70///
71/// Class { ^ $list_of_subclasses }
72/// ```
73#[derive(Clone, PartialEq)]
74pub enum SubclassesSpecifier {
75    List(Vec<String>),
76    Var(String),
77    Empty(),
78}
79
80/// Class collection specification with an optional attribute:
81///
82/// ```text
83/// EntityClass {
84///     << ClassNameToCollect
85///     attribute! << AnotherClassNameToCollect
86/// }
87/// ```
88#[derive(Clone, PartialEq)]
89pub struct CollectionSpecifier {
90    pub class_name: String,
91    pub virtual_attribute: Option<CollectionAttribute>,
92}
93
94/// An attribute specification for collections:
95///
96/// ```text
97/// EntityClass {
98///     attribute! << AnotherClassNameToCollect
99/// }
100/// ```
101#[derive(Clone, PartialEq)]
102pub struct CollectionAttribute {
103    pub attr_name: String,
104    pub is_public: bool,
105    pub is_optional: bool,
106    pub is_array: bool,
107}
108
109/// Attr holds the generation command used to generate an attribute
110/// in an entity.
111///
112/// AttrCommand provides the command pattern interface to `apply` and
113/// `revert` the attribute logic, supporting rolling, re-rolling,
114/// and unrolling entities.
115///
116/// Attr is cloneable so it can be shared throughout the class hierarchy.
117/// (See the method `expand` in ClassBuilder).
118/// The `cmd` inside Attr is using std::rc::Rc for that purpose.
119#[derive(Clone)]
120pub struct Attr {
121    pub cmd: std::sync::Arc<dyn AttrCommand + Sync + Send>,
122    pub is_public: bool,
123    pub is_optional: bool,
124    pub is_array: bool,
125}
126
127/// AttrCommand can apply or revert attribute value generation of various kinds.
128/// Refer to `commands.rs` to learn more about the different types of AttrCommands.
129pub trait AttrCommand {
130    fn apply(
131        &self,
132        ctx: &mut Context,
133        instance: &SandboxBuilder,
134        tx: &mut ReadWriteTransaction,
135        entity: &str,
136    ) -> Result<()>;
137    fn revert(
138        &self,
139        ctx: &mut Context,
140        instance: &SandboxBuilder,
141        tx: &mut ReadWriteTransaction,
142        entity: &str,
143    ) -> Result<()>;
144    fn value(&self) -> Option<String> {
145        None
146    }
147}
148
149/// InjectCommand can inject or eject attributes or attribute overrides to entities
150/// picked, used or pointed-at.
151pub trait InjectCommand {
152    fn inject(
153        &self,
154        instance: &SandboxBuilder,
155        tx: &mut ReadWriteTransaction,
156        euid: &str,
157        caller: &str,
158    ) -> Result<()>;
159    fn eject(
160        &self,
161        instance: &SandboxBuilder,
162        tx: &mut ReadWriteTransaction,
163        euid: &str,
164        caller: &str,
165    ) -> Result<()>;
166}
167
168#[derive(Clone)]
169pub struct Injectors {
170    pub prependers: Vec<Arc<dyn InjectCommand + Send + Sync>>,
171    pub appenders: Vec<Arc<dyn InjectCommand + Send + Sync>>,
172}
173
174/// Provides the toolset required to properly define a Scroll class and
175/// is primarily used by the Scroll parser.
176pub struct ClassBuilder {
177    pub name: String,
178    pub parent: String,
179    pub attrs: IndexMap<String, Attr>,
180    pub subclasses: SubclassesSpecifier,
181    pub hierarchy: Vec<String>,
182    pub collects: Vec<CollectionSpecifier>,
183    pub html_body: Option<String>,
184    pub html_header: Option<String>,
185    expanded: bool,
186}
187
188impl ClassBuilder {
189    /// Creates a new `ClassBuilder` instance.
190    pub fn new() -> Self {
191        ClassBuilder {
192            name: String::new(),
193            parent: String::new(),
194            attrs: indexmap::IndexMap::new(),
195            subclasses: SubclassesSpecifier::Empty(),
196            hierarchy: vec![],
197            collects: vec![],
198            expanded: false,
199            html_body: None,
200            html_header: None,
201        }
202    }
203
204    /// Sets the name of the class. Adds the name to the hierarchy if not present.
205    pub fn name(&mut self, name: &str) -> &mut Self {
206        self.name = name.to_string();
207        if self.hierarchy.contains(&self.name) {
208            log::error!("Invalid hierarchy detected for class {}", name)
209        } else {
210            self.hierarchy.push(self.name.clone());
211        }
212        self
213    }
214
215    /// Adds or overrides an attribute for the class.
216    pub fn add_attr(&mut self, key: String, attr: Attr) -> &mut Self {
217        if self.attrs.contains_key(&key) {
218            let existing_attr = &self.attrs[&key];
219            if (
220                existing_attr.is_array,
221                existing_attr.is_public,
222                existing_attr.is_optional,
223            ) != (attr.is_array, attr.is_public, attr.is_optional)
224            {
225                log::warn!(
226                    "Overriding attribute {} in {} has conflicting metadata.",
227                    key,
228                    self.name
229                );
230            }
231            self.attrs[&key] = attr;
232        } else {
233            self.attrs.insert(key.to_owned(), attr);
234        }
235        self
236    }
237
238    /// Sets the HTML body content for the class.
239    pub fn html_body(&mut self, body: String) -> &mut Self {
240        self.html_body = Some(body);
241        self
242    }
243
244    /// Sets the HTML body content for the class.
245    pub fn html_header(&mut self, header: String) -> &mut Self {
246        self.html_header = Some(header);
247        self
248    }
249
250    /// Specifies the class names to collect as a list of subclasses.
251    pub fn subclass_item(&mut self, class_name_to_collect: &str) {
252        if let SubclassesSpecifier::List(subclasses_list) = &mut self.subclasses {
253            subclasses_list.push(class_name_to_collect.to_string());
254        } else {
255            self.subclasses = SubclassesSpecifier::List(vec![class_name_to_collect.to_string()]);
256        }
257    }
258
259    /// Specifies a single subclass to collect using a variable.
260    pub fn subclass_var(&mut self, class_name_to_collect: &str) {
261        self.subclasses = SubclassesSpecifier::Var(class_name_to_collect.to_string());
262    }
263
264    /// Collects the specified class name.
265    pub fn collect(&mut self, spec: CollectionSpecifier) {
266        self.collects.push(spec);
267    }
268
269    /// Expands the class with attributes from another class using its name.
270    pub fn expand(
271        &mut self,
272        instance: &SandboxInstance,
273        expand_with_class_name: &str,
274    ) -> &mut Self {
275        let expand_class = &instance.classes.get(expand_with_class_name).unwrap();
276        for (k, v) in &expand_class.attrs {
277            self.attrs.insert(k.to_string(), v.clone());
278        }
279        self.expanded = true;
280        self
281    }
282
283    /// Extends this class with attributes and properties of a parent class.
284    pub fn extends(&mut self, instance: &SandboxInstance, parent_class_name: &str) -> &mut Self {
285        self.parent = parent_class_name.to_string();
286        let mut parent_class_name_mut = parent_class_name;
287
288        while !parent_class_name_mut.is_empty() {
289            self.hierarchy.push(parent_class_name_mut.to_string());
290            let parent_class = instance.classes.get(parent_class_name_mut).unwrap();
291            parent_class_name_mut = if parent_class.hierarchy.len() < 2 {
292                ""
293            } else {
294                &parent_class.hierarchy[1]
295            };
296            if let Some(parent_html_body) = &parent_class.html_body {
297                self.html_body = Some(parent_html_body.clone());
298            }
299            if let Some(parent_html_header) = &parent_class.html_header {
300                self.html_header = Some(parent_html_header.clone());
301            }
302            self.collects = parent_class.collects.clone();
303        }
304        self
305    }
306
307    /// Applies pending expansions or parent relationships before finalizing the class.
308    pub fn conclude(&mut self, instance: &SandboxInstance) -> &mut Self {
309        if !self.expanded && !self.parent.is_empty() {
310            let my_attrs = take(&mut self.attrs);
311            self.expand(instance, &self.parent.clone());
312            self.attrs.extend(my_attrs);
313        }
314        self
315    }
316
317    /// Finalizes the construction of the class.
318    pub fn build(self) -> Class {
319        Class {
320            name: self.name,
321            attrs: self.attrs,
322            subclasses: self.subclasses,
323            hierarchy: self.hierarchy,
324            collects: self.collects,
325            html_body: self.html_body,
326            html_header: self.html_header,
327        }
328    }
329}
330
331impl Default for ClassBuilder {
332    fn default() -> Self {
333        Self::new()
334    }
335}
336
337#[derive(Debug, PartialEq)]
338pub struct AppendPayload<'a> {
339    pub class_override: Option<&'a str>,
340    pub appended_uid: Option<String>,
341}
342
343#[derive(Debug, PartialEq)]
344pub struct RerollPayload<'a> {
345    pub existing_uid: String,
346    pub class_override: Option<&'a str>,
347    pub new_uid: Option<String>,
348}
349
350/// Used when calling the generators to state which type of user
351/// operation are we trying to do. Can provide and store ephemeral
352/// state throughout the generation call.
353#[derive(Debug, PartialEq)]
354pub enum Context<'a> {
355    Rolling,
356    Appending(AppendPayload<'a>),
357    Rerolling(RerollPayload<'a>),
358    Unrolling,
359    Restoring,
360}