kconfig_represent/
item.rs

1/*
2 Cargo KConfig - KConfig parser
3 Copyright (C) 2022  Sjoerd van Leent
4
5--------------------------------------------------------------------------------
6
7Copyright Notice: Apache
8
9Licensed under the Apache License, Version 2.0 (the "License"); you may not use
10this file except in compliance with the License. You may obtain a copy of the
11License at
12
13   https://www.apache.org/licenses/LICENSE-2.0
14
15Unless required by applicable law or agreed to in writing, software distributed
16under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17CONDITIONS OF ANY KIND, either express or implied. See the License for the
18specific language governing permissions and limitations under the License.
19
20--------------------------------------------------------------------------------
21
22Copyright Notice: GPLv2
23
24This program is free software: you can redistribute it and/or modify
25it under the terms of the GNU General Public License as published by
26the Free Software Foundation, either version 2 of the License, or
27(at your option) any later version.
28
29This program is distributed in the hope that it will be useful,
30but WITHOUT ANY WARRANTY; without even the implied warranty of
31MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32GNU General Public License for more details.
33
34You should have received a copy of the GNU General Public License
35along with this program.  If not, see <https://www.gnu.org/licenses/>.
36
37--------------------------------------------------------------------------------
38
39Copyright Notice: MIT
40
41Permission is hereby granted, free of charge, to any person obtaining a copy of
42this software and associated documentation files (the “Software”), to deal in
43the Software without restriction, including without limitation the rights to
44use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
45the Software, and to permit persons to whom the Software is furnished to do so,
46subject to the following conditions:
47
48The above copyright notice and this permission notice shall be included in all
49copies or substantial portions of the Software.
50
51THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
52IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
53FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
54COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
55IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
56CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
57*/
58
59//! This file contains the implementation of a configuration item for the
60//! different types of objects which can be created.
61
62use crate::Info;
63use crate::Tristate;
64use crate::Variant;
65use crate::VariantDataType;
66use crate::{
67    ast_extra::AstExtra,
68    dependencies::{ItemDependency, ResolvedExpr, SelectAndImplyDependencyResolver},
69    ItemReference,
70};
71use kconfig_parser::{
72    ast::{
73        parser::{ConditionContainer, Container, ElementType},
74        ConfigType, Dependency, Element,
75    },
76    Ast, Symbol,
77};
78use std::{
79    collections::{HashMap, HashSet},
80    hash::Hash,
81};
82
83#[derive(Debug, Clone)]
84pub struct Item {
85    /// The name (if any) of the menu item
86    name: String,
87
88    /// Whether the menu is to be enabled
89    enabled: bool,
90
91    /// The help value of the configuration item, if not available,
92    /// it will be an empty string.
93    help: Vec<String>,
94
95    /// The parent of the current item, if any
96    parent: Option<String>,
97
98    /// Registers conditions (if statements surrounding the item)
99    conditions: HashSet<Dependency>,
100
101    /// Registers the element belonging to the item (if any)
102    maybe_elt: Option<Element>,
103
104    // The macro symbols are a pre-evaluated set of symbols, which have been evaluated
105    // and can be used in evaluation cycles as fixed dependencies or conditional
106    // statements
107    macro_symbols: HashSet<Symbol>,
108
109    // The configuration item associated to the item, if any. If the item describes
110    // a "menu", a configuration item will not be associated. if it describes a "menuconfig"
111    // of "config", a configuration item will be associated.
112    config_items: HashMap<ConfigType, ConfigItem>,
113
114    // The dependency map, which contains the ItemReferences of the items
115    // which the current item depends on using the "depends on" relationship.
116    dependencies: HashSet<ItemDependency>,
117
118    // Backward dependencies of which the items select me
119    selects_me: HashMap<String, Option<ResolvedExpr>>,
120
121    // Backward dependencies of which the items imply me
122    imply_me: HashMap<String, Option<ResolvedExpr>>,
123
124    // Forward dependencies of which the items are selected by me
125    selected: HashMap<String, Option<ResolvedExpr>>,
126
127    // Forward dependencies of which the items are implied by me
128    implied: HashMap<String, Option<ResolvedExpr>>,
129}
130
131impl PartialEq for Item {
132    fn eq(&self, other: &Self) -> bool {
133        self.name == other.name
134            && self.enabled == other.enabled
135            && self.help == other.help
136            && self.parent == other.parent
137            && self.conditions == other.conditions
138            && self.maybe_elt == other.maybe_elt
139    }
140}
141impl Eq for Item {}
142
143impl Item {
144    fn new(name: &str, macro_symbols: &HashSet<Symbol>) -> Self {
145        let name = name.to_string();
146        Self {
147            name,
148            enabled: true,
149            help: vec![],
150            conditions: HashSet::new(),
151            parent: None,
152            maybe_elt: None,
153            macro_symbols: macro_symbols.clone(),
154            config_items: HashMap::new(),
155            dependencies: HashSet::new(),
156            selects_me: HashMap::new(),
157            imply_me: HashMap::new(),
158            selected: HashMap::new(),
159            implied: HashMap::new(),
160        }
161    }
162
163    pub(crate) fn new_from_ast(
164        main_menu_name: &str,
165        macro_symbols: &HashSet<Symbol>,
166        ast: &Ast,
167        forward_dependency_resolver: &mut SelectAndImplyDependencyResolver,
168    ) -> HashMap<ItemReference, Self> {
169        let mut item = Item::new(main_menu_name, macro_symbols);
170        let mut items = HashMap::new();
171        let empty_conditions = HashSet::new();
172
173        for name in ast.element_names() {
174            if let Ok(elt) = ast.element_from_name(&name) {
175                let child_items = item.create_child(
176                    name,
177                    ast,
178                    elt,
179                    empty_conditions.clone(),
180                    forward_dependency_resolver,
181                );
182                items.extend(child_items.into_iter());
183            }
184        }
185
186        for condition in ast.conditional_blocks() {
187            for (_, elt) in condition.elements() {
188                let mut additional_conditions = HashSet::new();
189                additional_conditions.insert(condition.dependency());
190                items.extend(item.create_child(
191                    elt.name(),
192                    &ast,
193                    elt,
194                    additional_conditions,
195                    forward_dependency_resolver,
196                ));
197            }
198        }
199
200        items.insert(ItemReference::Menu(main_menu_name.to_string()), item);
201        items
202    }
203
204    pub(crate) fn init_from_elt(
205        &mut self,
206        ast: &Ast,
207        elt: &Element,
208        conditions: HashSet<Dependency>,
209        forward_dependency_resolver: &mut SelectAndImplyDependencyResolver,
210    ) -> HashMap<ItemReference, Self> {
211        let mut items = HashMap::new();
212
213        for (name, elt) in elt.sub_elements() {
214            let child_items = self.create_child(
215                name,
216                ast,
217                elt,
218                conditions.clone(),
219                forward_dependency_resolver,
220            );
221            items.extend(child_items.into_iter());
222        }
223
224        for condition in elt.conditional_blocks() {
225            for (_, elt) in condition.elements() {
226                let mut additional_conditions = conditions.clone();
227                additional_conditions.insert(condition.dependency());
228                items.extend(self.create_child(
229                    elt.name(),
230                    &ast,
231                    elt,
232                    additional_conditions,
233                    forward_dependency_resolver,
234                ));
235            }
236        }
237
238        items
239    }
240
241    fn create_child(
242        &mut self,
243        name: String,
244        ast: &Ast,
245        elt: Element,
246        conditions: HashSet<Dependency>,
247        forward_dependency_resolver: &mut SelectAndImplyDependencyResolver,
248    ) -> HashMap<ItemReference, Self> {
249        let mut item = Self::new(&name, &self.macro_symbols);
250        item.conditions = conditions.clone();
251        for paragraph in elt.help() {
252            item.help.push(paragraph);
253        }
254
255        item.parent = Some(self.name.clone());
256        let mut items = item.init_from_elt(ast, &elt, conditions, forward_dependency_resolver);
257
258        let itemref = match &elt.elt_type() {
259            ElementType::Config => ItemReference::Config(name.clone()),
260            ElementType::MenuConfig => ItemReference::MenuConfig(name.clone()),
261            ElementType::Menu => ItemReference::Menu(name.clone()),
262        };
263
264        item.maybe_elt = Some(elt.clone());
265
266        if let Some(types) = elt.types() {
267            for (config_type, _) in types {
268                if let Some(config) = ConfigItem::new(config_type, &elt) {
269                    item.config_items.insert(config_type.clone(), config);
270                }
271            }
272        }
273
274        if let Some(reverse_dependencies) = elt.reverse_dependencies() {
275            for (selected_name, maybe_condition) in reverse_dependencies {
276                forward_dependency_resolver.put_select(&itemref, selected_name, maybe_condition)
277            }
278        }
279
280        if let Some(weak_dependencies) = elt.weak_dependencies() {
281            for (implied_name, maybe_condition) in weak_dependencies {
282                forward_dependency_resolver.put_imply(&itemref, implied_name, maybe_condition)
283            }
284        }
285
286        items.insert(itemref, item);
287        items
288    }
289
290    pub(crate) fn set_dependencies(&mut self, dependencies: HashSet<ItemDependency>) {
291        self.dependencies = dependencies;
292    }
293
294    pub(crate) fn set_selects_me(&mut self, dependencies: HashMap<String, Option<ResolvedExpr>>) {
295        self.selects_me = dependencies;
296    }
297
298    pub(crate) fn set_selected(&mut self, dependencies: HashMap<String, Option<ResolvedExpr>>) {
299        self.selected = dependencies;
300    }
301
302    pub(crate) fn set_imply_me(&mut self, dependencies: HashMap<String, Option<ResolvedExpr>>) {
303        self.imply_me = dependencies;
304    }
305
306    pub(crate) fn set_implied(&mut self, dependencies: HashMap<String, Option<ResolvedExpr>>) {
307        self.implied = dependencies;
308    }
309
310    /// Returns the name of the item, typically, this is the same as the name of
311    /// the associated ItemReference
312    pub fn name<'a>(&'a self) -> &'a str {
313        &self.name
314    }
315
316    /// Returns the item reference of the item, which can be used to traverse
317    /// the configuration registry, or perform dependency lookups in other items
318    pub fn itemref(&self) -> ItemReference {
319        match &self.maybe_elt {
320            Some(elt) => match elt.elt_type() {
321                ElementType::Config => ItemReference::Config(self.name.clone()),
322                ElementType::MenuConfig => ItemReference::MenuConfig(self.name.clone()),
323                ElementType::Menu => ItemReference::Menu(self.name.clone()),
324            },
325            // Possibly the main menu
326            None => ItemReference::Menu(self.name.clone()),
327        }
328    }
329
330    pub fn config_items<'a>(&'a self) -> &'a HashMap<ConfigType, ConfigItem> {
331        &self.config_items
332    }
333
334    pub fn config_items_mut<'a>(&'a mut self) -> &'a mut HashMap<ConfigType, ConfigItem> {
335        &mut self.config_items
336    }
337
338    pub fn conditions(&self) -> HashSet<Dependency> {
339        self.conditions.clone()
340    }
341
342    /// Returns the reverse dependencies, without evaluation
343    pub fn item_dependencies(&self) -> HashSet<ItemDependency> {
344        self.dependencies.clone()
345    }
346
347    /// Returns the items selected by me
348    pub fn selected(&self) -> HashMap<String, Option<ResolvedExpr>> {
349        self.selected.clone()
350    }
351
352    /// Returns the items implied by me
353    pub fn implied<'a>(&self) -> HashMap<String, Option<ResolvedExpr>> {
354        self.implied.clone()
355    }
356
357    /// Returns the items selecting me
358    pub fn selects_me(&self) -> HashMap<String, Option<ResolvedExpr>> {
359        self.selects_me.clone()
360    }
361
362    /// Returns the items implying me
363    pub fn imply_me(&self) -> HashMap<String, Option<ResolvedExpr>> {
364        self.imply_me.clone()
365    }
366
367    pub fn raw_elt<'a>(&'a self) -> &'a Option<Element> {
368        &self.maybe_elt
369    }
370
371    pub fn parent<'a>(&'a self) -> &'a Option<String> {
372        &self.parent
373    }
374
375    pub fn info(&self) -> Info {
376        Info::new(self.help.clone(), self.itemref())
377    }
378
379    pub(crate) fn collect_all_dependencies(&self) -> HashSet<MaybeIncompleteItemReference> {
380        let mut itemrefs: HashSet<MaybeIncompleteItemReference> = HashSet::new();
381
382        for item_dependency in self.item_dependencies() {
383            for itemref in item_dependency.expr().itemrefs() {
384                itemrefs.insert(MaybeIncompleteItemReference::ItemReference(itemref.clone()));
385            }
386        }
387
388        for (name, expr) in self.imply_me() {
389            itemrefs.insert(MaybeIncompleteItemReference::String(name));
390            if let Some(expr) = expr {
391                for itemref in expr.itemrefs() {
392                    itemrefs.insert(MaybeIncompleteItemReference::ItemReference(itemref.clone()));
393                }
394            }
395        }
396
397        for (name, expr) in self.selects_me() {
398            itemrefs.insert(MaybeIncompleteItemReference::String(name));
399            if let Some(expr) = expr {
400                for itemref in expr.itemrefs() {
401                    itemrefs.insert(MaybeIncompleteItemReference::ItemReference(itemref.clone()));
402                }
403            }
404        }
405
406        for (_, config_item) in self.config_items() {
407            for itemref in config_item.collect_all_dependencies() {
408                itemrefs.insert(MaybeIncompleteItemReference::ItemReference(itemref.clone()));
409            }
410        }
411
412        itemrefs
413    }
414}
415
416/// Represents an incomplete item reference, that is, it can contain complete
417/// itemreferences, or to be resolved item references of which it is not known
418/// yet whether it should be a config, menuconfig, or menu item.
419#[derive(Clone, Debug, PartialEq, Eq, Hash)]
420pub(crate) enum MaybeIncompleteItemReference {
421    ItemReference(ItemReference),
422    String(String),
423}
424
425/// Represents a configuration item as defined by the Abstract Syntax
426/// Tree, received from the kconfig-parser module.
427#[derive(Clone, Debug, PartialEq, Eq)]
428pub struct ConfigItem {
429    /// The prompt of the configuration item
430    prompt: Option<String>,
431
432    /// Describes whether the type at hand is a normal config type,
433    /// or a menuconfig type. Typically, dependencies on menuconfig types,
434    /// should be displayed as if the configuration item itself is a menu.
435    is_menu: bool,
436
437    /// Value of the configuration item, if set to None, it is not set,
438    /// and the default should be used instead.
439    value: Option<Variant>,
440
441    /// Configuration value of the configuration item as set by reading
442    /// the configuration.
443    config_value: Option<Variant>,
444
445    /// The configuration value type
446    config_type: ConfigType,
447
448    /// The default value of the configuration item, where available
449    defaults: HashMap<ResolvedExpr, Option<ResolvedExpr>>,
450
451    /// The dependencies on which the availability of the current configuration
452    /// item depends.
453    maybe_expr: Option<ResolvedExpr>,
454
455    /// If the configuration type is indicating a Boolean or Tristate
456    /// value, then the select and imply reverse dependencies can enforce
457    /// a maximum of choices. If the choices are determined through select,
458    /// then the boolean will be true, if the choices are determined through
459    /// imply, then to boolean wioll be false.
460    choices: Option<(bool, Tristate)>,
461}
462
463impl ConfigItem {
464    pub(crate) fn new(config_type: &ConfigType, elt: &Element) -> Option<Self> {
465        let maybe_is_menu = match elt.elt_type() {
466            ElementType::Config => Some(false),
467            ElementType::MenuConfig => Some(true),
468            ElementType::Menu => None,
469        };
470
471        maybe_is_menu.map(|is_menu| Self {
472            maybe_expr: None,
473            is_menu,
474            config_value: None,
475            value: None,
476            choices: match config_type {
477                ConfigType::Bool | ConfigType::Tristate => Some((true, Tristate::FALSE)),
478                _ => None,
479            },
480            config_type: config_type.clone(),
481            prompt: elt.prompt(),
482            defaults: HashMap::new(),
483        })
484    }
485
486    /// Returns the value belonging to the configuration item, if a value is
487    /// set by the user.
488    pub(crate) fn value(&self) -> Option<Variant> {
489        self.value.clone()
490    }
491
492    /// Sets the value for the configuration item
493    pub(crate) fn set_value(&mut self, value: Variant) {
494        self.value = Some(value.transform(VariantDataType::from(&self.config_type)))
495    }
496
497    /// Sets the configuration value from a .config file
498    pub(crate) fn set_config_value(&mut self, value: Variant) {
499        self.config_value = Some(value.transform(VariantDataType::from(&self.config_type)))
500    }
501
502    /// Returns the configuration value as read from a .config file
503    pub(crate) fn config_value(&self) -> Option<Variant> {
504        self.config_value.clone()
505    }
506
507    /// Returns whether the choices have been selected or implied, if at all
508    /// selectable or implyable
509    pub fn choice_mode(&self) -> Option<bool> {
510        match self.choices {
511            Some((selected, _)) => Some(selected),
512            None => None,
513        }
514    }
515
516    /// Returns the lower bounds of the selected value
517    pub fn choice_low(&self) -> Variant {
518        match self.choices {
519            Some((_, bound)) => Variant::from(bound),
520            None => Variant::from(false),
521        }
522    }
523
524    // Sets the choice mode
525    pub(crate) fn set_choice_mode(&mut self, selected: bool, low_bound: Tristate) {
526        self.choices = Some((selected, low_bound));
527    }
528
529    // Removes the value, and let's the configuration item revert to the default value
530    pub(crate) fn reset_value(&mut self) {
531        self.value = None
532    }
533
534    // Removes the configuration value
535    pub(crate) fn reset_config_value(&mut self) {
536        self.config_value = None
537    }
538
539    // Removes both the value as well as the configuration value,
540    // reverts the selected/implied choices, etc.
541    pub(crate) fn reset_all(&mut self) {
542        self.reset_value();
543        self.reset_config_value();
544        if let Some(_) = self.choices {
545            self.choices = Some((true, Tristate::FALSE));
546        }
547    }
548
549    /// Returns the configuration type of the configuration item
550    pub fn config_type(&self) -> ConfigType {
551        self.config_type.clone()
552    }
553
554    /// Sets the configuration item dependencies
555    pub(crate) fn set_dependencies(&mut self, resolved_expr: ResolvedExpr) {
556        self.maybe_expr = Some(resolved_expr);
557    }
558
559    pub(crate) fn maybe_dependencies(&self) -> Option<ResolvedExpr> {
560        match &self.maybe_expr {
561            Some(expr) => Some(expr.clone()),
562            None => None,
563        }
564    }
565
566    pub(crate) fn add_default(
567        &mut self,
568        default_expr: ResolvedExpr,
569        dependency: Option<ResolvedExpr>,
570    ) {
571        self.defaults.insert(default_expr, dependency);
572    }
573
574    pub(crate) fn defaults(&self) -> HashMap<ResolvedExpr, Option<ResolvedExpr>> {
575        let mut map = HashMap::new();
576        for (k, v) in &self.defaults {
577            let cloned = match v {
578                Some(v) => Some(v.clone()),
579                None => None,
580            };
581            map.insert(k.clone(), cloned);
582        }
583        map
584    }
585
586    /// Returns the prompt string, if any is avilable. If no prompt is available,
587    /// the value can not be set through exterior commands, but requires to be set
588    /// by dependencies only.
589    pub fn prompt<'a>(&'a self) -> &'a Option<String> {
590        &self.prompt
591    }
592
593    /// Returns whether the current item is also a menu
594    pub fn is_menu(&self) -> bool {
595        self.is_menu
596    }
597
598    /// Returns all the dependencies of the configuration item
599    pub fn collect_all_dependencies(&self) -> HashSet<ItemReference> {
600        let mut itemrefs: HashSet<ItemReference> = HashSet::new();
601        for (k, v) in &self.defaults {
602            for itemref in k.itemrefs() {
603                itemrefs.insert(itemref.clone());
604            }
605            if let Some(expr) = v {
606                for itemref in expr.itemrefs() {
607                    itemrefs.insert(itemref.clone());
608                }
609            }
610        }
611
612        if let Some(expr) = &self.maybe_expr {
613            for itemref in expr.itemrefs() {
614                itemrefs.insert(itemref.clone());
615            }
616        }
617
618        itemrefs
619    }
620}
621
622impl Hash for Item {
623    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
624        self.name.hash(state);
625        self.enabled.hash(state);
626        self.help.hash(state);
627        for parent in self.conditions.iter() {
628            parent.hash(state)
629        }
630    }
631}