Skip to main content

workshop_rs/settings/
mod.rs

1//! The neutral settings carrier.
2//!
3//! A typed, non-serde tree for custom-game-settings blocks shared by
4//! validation and emission. The tree is a carrier: settings are carried and
5//! emitted, never interpreted by lowering/analysis. Source-backed path and
6//! display data remain implementation details behind this module's APIs.
7//!
8//! Extracted from the Wright-authored `wright-ir` crate; see
9//! [`docs/provenance.md`](https://github.com/wrightkit/workshop-rs/blob/main/docs/provenance.md).
10
11pub(crate) mod check;
12pub(crate) mod emitter;
13mod member;
14pub(crate) mod parser;
15
16pub(crate) mod reconciliation;
17pub(crate) mod schema;
18pub(crate) mod table;
19
20/// A segment of a path accepted by settings schema lookups.
21#[derive(Debug, Clone, Copy, Hash)]
22pub enum PathPart<'a> {
23    /// A literal key (mode names under `gamemodes` are literal keys too:
24    /// per-key subsets are exact-path entries).
25    Part(&'a str),
26    /// Any team slot (allTeams).
27    Team,
28    /// Any hero-config slot.
29    Hero,
30}
31
32impl<'b> PartialEq<PathPart<'b>> for PathPart<'_> {
33    fn eq(&self, other: &PathPart<'b>) -> bool {
34        match (self, other) {
35            (PathPart::Part(left), PathPart::Part(right)) => left == right,
36            (PathPart::Team, PathPart::Team) => true,
37            (PathPart::Hero, PathPart::Hero) => true,
38            _ => false,
39        }
40    }
41}
42
43impl Eq for PathPart<'_> {}
44
45pub use check::{
46    DiagnosticSeverity, SettingsDiagnostic, check_emission, check_emission_diagnostics,
47};
48pub use schema::{
49    Applicability, EffectiveNumber, NumericBounds, NumericBoundsError, SettingDefinition,
50    SettingEnumMember, SettingId, SettingIdentity, SettingOccurrence, SettingOperationError,
51    SettingPresentation, SettingScope, SettingSource, SettingSourceEdit, SettingSourceKind,
52    SettingTarget, SettingTargetKind, SettingValue, SettingValueDomain, TeamId, definition,
53    definitions, definitions_by_id, validate_catalog,
54};
55pub use table::{LocalizedSettingName, localized_setting_names};
56
57use crate::core::source::Span;
58
59/// A settings block: `settings { ... }` with its typed children.
60#[derive(Debug, Clone)]
61pub struct Settings {
62    pub span: Option<Span>,
63    pub children: Vec<SettingsNode>,
64}
65
66/// One member of a settings group.
67#[derive(Debug, Clone)]
68pub enum SettingsNode {
69    /// User-authored mode data under `settings.workshop`.
70    Workshop {
71        children: Vec<SettingsNode>,
72        span: Option<Span>,
73    },
74    Group {
75        name: String,
76        children: Vec<SettingsNode>,
77        span: Option<Span>,
78    },
79    Number {
80        name: String,
81        value: f64,
82        span: Option<Span>,
83    },
84    Bool {
85        name: String,
86        value: bool,
87        span: Option<Span>,
88    },
89    /// A presence-only Workshop extension setting (for example `Beam Effects`).
90    Flag { name: String, span: Option<Span> },
91    String {
92        name: String,
93        value: String,
94        span: Option<Span>,
95    },
96    List {
97        name: String,
98        elements: Vec<SettingsListElement>,
99        span: Option<Span>,
100    },
101    /// A syntactically valid settings member whose semantic catalog entry is
102    /// not yet declared. The raw value is carried explicitly so parsing does
103    /// not fabricate a type or silently discard project settings.
104    Raw {
105        name: String,
106        value: String,
107        span: Option<Span>,
108    },
109    /// A member written as `name: value` with the value as authored and never
110    /// interpreted. At a catalogued path `name` is the canonical key and is
111    /// emitted in the target locale; elsewhere it is written as is. Carries
112    /// catalogued keys with values the catalog does not declare, and members
113    /// whose empty value still needs its `:`.
114    RawValue {
115        name: String,
116        value: String,
117        span: Option<Span>,
118    },
119    /// A member written as `name { ... }` with the block emitted verbatim:
120    /// the pass-through marker for non-scalar members at positions where a
121    /// plain `Group` is name-resolved or kind-checked (a hero group under a
122    /// team, a member block under a catalogued key). The name is written as
123    /// authored and children carry their written form; a typed leaf inside
124    /// still fails the written-form checks the opaque-group path applies.
125    RawGroup {
126        name: String,
127        children: Vec<SettingsNode>,
128        span: Option<Span>,
129    },
130}
131
132/// One element of a settings list (corpus lists are all strings).
133#[derive(Debug, Clone)]
134pub struct SettingsListElement {
135    /// The element value: the resolved catalog key for a declared element
136    /// (`workshopChamber`), or the authored text when nothing resolves.
137    pub value: String,
138    /// The variant qualifier a `<map name> <variant id>` element carries:
139    /// the authored space-separated variant ids (`"972777519512064577"`).
140    /// `None` on an unqualified element. Only map lists take a variant
141    /// qualifier.
142    pub variant: Option<String>,
143    pub span: Option<Span>,
144}
145
146impl SettingsListElement {
147    /// The variant ids of a variant-qualified element, in authored order —
148    /// one id per qualifier token. Empty for an unqualified element.
149    pub fn variant_ids(&self) -> impl Iterator<Item = &str> {
150        self.variant.iter().flat_map(|variant| variant.split(' '))
151    }
152
153    fn clear_span(&mut self) {
154        self.span = None;
155    }
156}
157
158impl Settings {
159    pub(crate) fn clear_spans(&mut self) {
160        self.span = None;
161        for node in &mut self.children {
162            node.clear_spans();
163        }
164    }
165}
166
167impl SettingsNode {
168    /// The source span of this node, if any.
169    pub fn span(&self) -> Option<Span> {
170        match self {
171            SettingsNode::Workshop { span, .. }
172            | SettingsNode::Group { span, .. }
173            | SettingsNode::Number { span, .. }
174            | SettingsNode::Bool { span, .. }
175            | SettingsNode::Flag { span, .. }
176            | SettingsNode::String { span, .. }
177            | SettingsNode::List { span, .. }
178            | SettingsNode::Raw { span, .. }
179            | SettingsNode::RawValue { span, .. }
180            | SettingsNode::RawGroup { span, .. } => *span,
181        }
182    }
183
184    fn clear_spans(&mut self) {
185        match self {
186            SettingsNode::Workshop { children, span }
187            | SettingsNode::Group { children, span, .. }
188            | SettingsNode::RawGroup { children, span, .. } => {
189                *span = None;
190                for node in children {
191                    node.clear_spans();
192                }
193            }
194            SettingsNode::Number { span, .. }
195            | SettingsNode::Bool { span, .. }
196            | SettingsNode::Flag { span, .. }
197            | SettingsNode::String { span, .. }
198            | SettingsNode::Raw { span, .. }
199            | SettingsNode::RawValue { span, .. } => *span = None,
200            SettingsNode::List { elements, span, .. } => {
201                *span = None;
202                for element in elements {
203                    element.clear_span();
204                }
205            }
206        }
207    }
208
209    /// The key name of this node.
210    pub fn name(&self) -> &str {
211        match self {
212            SettingsNode::Workshop { .. } => "workshop",
213            SettingsNode::Group { name, .. }
214            | SettingsNode::Number { name, .. }
215            | SettingsNode::Bool { name, .. }
216            | SettingsNode::Flag { name, .. }
217            | SettingsNode::String { name, .. }
218            | SettingsNode::List { name, .. }
219            | SettingsNode::Raw { name, .. }
220            | SettingsNode::RawValue { name, .. }
221            | SettingsNode::RawGroup { name, .. } => name,
222        }
223    }
224}