kconfig_represent/config_registry.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 implements the registry, which is the main component of the Kconfig
60//! representation module.
61
62use crate::dependencies::ResolvedExpr;
63use crate::error::LoadError;
64use crate::item::ConfigItem;
65use crate::Tristate;
66use std::io::BufRead;
67use std::io::BufReader;
68use std::io::Lines;
69use std::io::Read;
70
71use crate::{
72 dependencies::{DependencyResolver, SelectAndImplyDependencyResolver},
73 error::Error,
74 eval::Evaluator,
75 eval::ExpensiveEvaluator,
76 item::MaybeIncompleteItemReference,
77 Ast, AstExtra, Info, Item, ItemReference, MutationStart, Symbol, Variant,
78};
79use std::collections::{HashMap, HashSet};
80use std::fs::OpenOptions;
81use std::io::Write;
82
83/// The ConfigRegistry is used to create a registry, which allows reading and writing
84/// a configuration to and from a .config file as defined by a Kconfig file. To
85/// present the menu, a frontend is necessary to represent the state of the registry.
86#[derive(Debug)]
87pub struct ConfigRegistry {
88 // An item can either be a configuration item, a menu item, or both (in the case
89 // of a "menuconfig" situation). The different items can be retrieved by a
90 // menu item descriptor, and resolved as such.
91 items: HashMap<ItemReference, Item>,
92
93 // Name of the main menu
94 main_menu_name: String,
95}
96
97impl ConfigRegistry {
98 /// Constructs a new configuration registry, given the Abstract syntax tree
99 /// as loaded from a kconfig-parser's Ast instance, and the macro symbols
100 /// discoverd by the macro lexer (if used).
101 pub fn new(ast: &Ast, macro_symbols: &HashSet<Symbol>) -> Result<Self, Error> {
102 let mut forward_dependency_resolver = SelectAndImplyDependencyResolver::new();
103 let mut main_menu = Self {
104 items: HashMap::new(),
105 main_menu_name: ast.main_menu_name_with_default(),
106 };
107
108 let items = Item::new_from_ast(
109 &main_menu.main_menu_name,
110 macro_symbols,
111 &ast,
112 &mut forward_dependency_resolver,
113 );
114
115 main_menu.items.extend(items.into_iter());
116
117 DependencyResolver::new(forward_dependency_resolver, macro_symbols.clone())
118 .resolve(&mut main_menu.items);
119
120 main_menu.reset()?;
121 Ok(main_menu)
122 }
123
124 /// Return a reference to the map of the items available in the configuration registry
125 pub fn items<'a>(&'a self) -> &'a HashMap<ItemReference, Item> {
126 &self.items
127 }
128
129 /// Returns the name of the main menu
130 pub fn main_menu_name<'a>(&'a self) -> &'a str {
131 &self.main_menu_name
132 }
133
134 /// Returns the item belonging to the main menu
135 pub fn main_menu_item<'a>(&'a self) -> &'a Item {
136 let itemref = ItemReference::Menu(self.main_menu_name.clone());
137 // The menu should be in the item map, otherwise something very strange is going on
138 self.get(&itemref).unwrap()
139 }
140
141 /// Finds the item given the item reference, if available, otherwise returns None
142 pub fn get<'a>(&'a self, itemref: &ItemReference) -> Option<&'a Item> {
143 self.items.get(itemref)
144 }
145
146 /// Returns an item's information, if available, otherwise returns None
147 pub fn get_info(&self, itemref: &ItemReference) -> Option<Info> {
148 self.get(itemref).map(|item| item.info())
149 }
150
151 /// Given the item reference, determine whether the associated item is enabled
152 pub fn item_enabled(&self, itemref: &ItemReference) -> Result<bool, Error> {
153 match self.items.get(itemref) {
154 Some(item) => {
155 let evaluator = Evaluator::new(&self.items);
156 let enabled_variant = evaluator.enabled(item);
157 enabled_variant.map(Variant::into)
158 }
159 None => Err(Error::from(format!(
160 "Could not find item associated to itemreference {itemref}"
161 ))),
162 }
163 }
164
165 fn cloned_int_current_config_item(
166 item: Item,
167 expensive_evaluator: &ExpensiveEvaluator,
168 ) -> Option<ConfigItem> {
169 let mut restricted_config_item: Option<&ConfigItem> = None;
170 let mut unrestricted_config_item: Option<&ConfigItem> = None;
171
172 for (_, config_item) in item.config_items() {
173 if let Some(expr) = config_item.maybe_dependencies() {
174 if let Ok(variant) = expensive_evaluator.evalexpr(expr.item_expr()) {
175 let enabled: bool = variant.into();
176 if enabled {
177 restricted_config_item = Some(config_item);
178 }
179 }
180 } else {
181 unrestricted_config_item = Some(config_item);
182 }
183 }
184
185 match (restricted_config_item, unrestricted_config_item) {
186 (Some(config_item), _) => Some(config_item.clone()),
187 (_, Some(config_item)) => Some(config_item.clone()),
188 _ => None,
189 }
190 }
191
192 fn int_current_config_item<'a>(&self, item: &'a Item) -> Option<&'a ConfigItem> {
193 let mut restricted_config_item: Option<&ConfigItem> = None;
194 let mut unrestricted_config_item: Option<&ConfigItem> = None;
195
196 for (_, config_item) in item.config_items() {
197 if let Some(expr) = config_item.maybe_dependencies() {
198 let evaluator = Evaluator::new(&self.items);
199 if let Ok(variant) = evaluator.evalexpr(expr.item_expr()) {
200 let enabled: bool = variant.into();
201 if enabled {
202 restricted_config_item = Some(config_item);
203 }
204 }
205 } else {
206 unrestricted_config_item = Some(config_item);
207 }
208 }
209
210 match (restricted_config_item, unrestricted_config_item) {
211 (Some(config_item), _) => Some(&config_item),
212 (_, Some(config_item)) => Some(&config_item),
213 _ => None,
214 }
215 }
216
217 /// Returns a clone of the active configuration item, if available
218 pub fn current_config_item(&self, itemref: &ItemReference) -> Option<ConfigItem> {
219 self.get(itemref)
220 .map(|item| self.int_current_config_item(item))
221 .map(|c| c.map(ConfigItem::clone))
222 .flatten()
223 }
224
225 /// Returns how to start a mutation. Given the configuration type, this can be
226 /// a different start situation.
227 pub fn mutation_start(&self, itemref: &ItemReference) -> Option<MutationStart> {
228 self.current_value(itemref).map(MutationStart::from)
229 }
230
231 /// The current value of an item is resolved according to a number of rules:
232 ///
233 /// If the value has been selected through a select reverse dependency, then
234 /// this value will be used, with respect to it's default value.
235 ///
236 /// If the value has a "regular" value, then this value will be used
237 ///
238 /// If the value has been selected through an implied value, then this
239 /// value will be used, with respect to it's default value.
240 ///
241 /// Otherwise, the default value will be used.
242 pub fn current_value(&self, itemref: &ItemReference) -> Option<Variant> {
243 Evaluator::new(&self.items).current_value(itemref)
244 }
245
246 /// Determines whether the value if the configuration item internal to the item
247 /// is set to it's default value.
248 pub fn current_value_is_default(&self, itemref: &ItemReference) -> bool {
249 let maybe_config_item = self.current_config_item(itemref);
250 maybe_config_item
251 .map(|config_item| match config_item.value() {
252 Some(_) => false,
253 None => true,
254 })
255 .unwrap_or(false)
256 }
257
258 fn int_set_value(&mut self, itemref: &ItemReference, value: Variant) -> Result<(), Error> {
259 // If the itemreference is of type menu, this is a nonsense situation
260 if let &ItemReference::Menu(_) = itemref {
261 return Err(Error::from(format!(
262 "Item reference {itemref} indicates a menu, which does not have values"
263 )));
264 }
265
266 // Get the item, if available
267 let item = match self.items.get_mut(itemref) {
268 // If no item is available, then an error should be raised
269 None => {
270 return Err(Error::from(format!(
271 "Item for reference not found: {itemref}"
272 )))
273 }
274 Some(item) => item,
275 };
276
277 // Force the value of each configuration item within the current item
278 for (_, config_item) in item.config_items_mut() {
279 config_item.set_config_value(value.clone());
280 }
281
282 Ok(())
283 }
284
285 fn int_discard_value(&mut self, itemref: &ItemReference) -> Result<(), Error> {
286 // If the itemreference is of type menu, this is a nonsense situation
287 if let &ItemReference::Menu(_) = itemref {
288 return Err(Error::from(format!(
289 "Item reference {itemref} indicates a menu, which does not have values"
290 )));
291 }
292
293 // Get the item, if available
294 let item = match self.items.get_mut(itemref) {
295 // If no item is available, then an error should be raised
296 None => {
297 return Err(Error::from(format!(
298 "Item for reference not found: {itemref}"
299 )))
300 }
301 Some(item) => item,
302 };
303
304 // Force the value of each configuration item within the current item
305 for (_, config_item) in item.config_items_mut() {
306 config_item.reset_value();
307 }
308
309 Ok(())
310 }
311
312 /// Sets the value of a configuration item either by loading it from an
313 /// earlier stored session, or by entering the data programmatically/through
314 /// a configuration editor. Returns nothing if successful, or an error if
315 /// for some reason the value could not be set properly.
316 pub fn set_value(&mut self, itemref: &ItemReference, value: Variant) -> Result<(), Error> {
317 self.int_set_value(itemref, value)?;
318 self.reset()
319 }
320
321 /// Unsets the value of a configuation item
322 pub fn discard_value(&mut self, itemref: &ItemReference) -> Result<(), Error> {
323 self.int_discard_value(itemref)?;
324 self.reset()
325 }
326
327 /// Finds the appropriate item reference by the name of the itemreference
328 pub fn find_itemref(&self, name: &str) -> Option<ItemReference> {
329 let key1 = ItemReference::Config(name.to_string());
330 let key2 = ItemReference::MenuConfig(name.to_string());
331 let key3 = ItemReference::Menu(name.to_string());
332 if self.items.contains_key(&key1) {
333 Some(key1)
334 } else if self.items.contains_key(&key2) {
335 Some(key2)
336 } else if self.items.contains_key(&key3) {
337 Some(key3)
338 } else {
339 None
340 }
341 }
342
343 /// Evaluates all items and enforces the proper rules on all internal configuration
344 /// items and dependent configuration items for all the items evaluated.
345 pub fn reset(&mut self) -> Result<(), Error> {
346 // Items are reset by evaluating them, from the top-most item, to the item with the
347 // most dependencies.
348
349 // First, collect all the item references, and the dependencies of those item references
350 let mut snapshots: HashMap<ItemReference, HashSet<ItemReference>> = HashMap::new();
351 for (itemref, item) in &self.items {
352 let maybe_incomplete_snapshot = item.collect_all_dependencies();
353 let snapshot = maybe_incomplete_snapshot
354 .iter()
355 .map(|iiref| match iiref {
356 MaybeIncompleteItemReference::ItemReference(itemref) => itemref.clone(),
357 MaybeIncompleteItemReference::String(s) => self
358 .find_itemref(&s)
359 .unwrap_or(ItemReference::Config(s.to_string())),
360 })
361 .collect();
362 snapshots.insert(itemref.clone(), snapshot);
363 }
364
365 // Resolve the item references which have no further dependencies, then resolve the
366 // next items, etc. until the list of snapshots is depleted. If the list of snapshots
367 // does not deplete any more, then there is a cyclic dependency, which is to result
368 // into an error.
369 while snapshots.len() > 0 {
370 match Self::take_available_snapshot(&snapshots) {
371 Some(itemref) => {
372 self.reset_item_by_ref(itemref.clone());
373 snapshots.remove(&itemref);
374 }
375 None => {
376 if snapshots.len() > 0 {
377 let keys: HashSet<ItemReference> = snapshots.keys().cloned().collect();
378 let mut error_string = String::new();
379 for key in keys {
380 if error_string.len() > 0 {
381 error_string += " ";
382 }
383 error_string += &key.to_string();
384 }
385 return Err(Error::from(format!(
386 "Dependency cycle(s) detected in items {error_string}"
387 )));
388 }
389 }
390 }
391 }
392
393 Ok(())
394 }
395
396 fn reset_item_by_ref(&mut self, itemref: ItemReference) {
397 let map: HashMap<ItemReference, Item> = self
398 .items
399 .iter()
400 .map(|(itemref, item)| (itemref.clone(), item.clone()))
401 .collect();
402
403 if let Some(item) = self.items.get_mut(&itemref) {
404 // For the item:
405 // Determine if the item is enabled, an item is only enabled if all the
406 // item dependencies evaluate true.
407 //
408 // Determine which current configuration item is active, all other configuration
409 // will be defaulted.
410 // - Determine if the value of the current configuration item is
411 // selected and/or implied, and resolve the choices
412 // - Determine if the value has a default value matching the configured value,
413 // if so, erase the configured value
414 // - If no default value matches the configured value, set the configured value
415
416 let evaluator = ExpensiveEvaluator::new(map);
417 let enabled_variant = evaluator.enabled(item);
418 let is_enabled = enabled_variant.map(Variant::into).unwrap_or(false);
419
420 // If the item is not enabled, all the configuration values set by externally
421 // are to be discarded.
422 if !is_enabled {
423 for value in item.config_items_mut().values_mut() {
424 value.reset_all();
425 }
426 } else {
427 if let Some(current_config_item) =
428 Self::cloned_int_current_config_item(item.clone(), &evaluator)
429 {
430 let selects_me = item.selects_me();
431 let imply_me = item.imply_me();
432
433 for (_, config_item) in item.config_items_mut() {
434 let is_current = config_item == ¤t_config_item;
435 let default_value =
436 evaluator.config_item_default_value(¤t_config_item);
437 match config_item.choice_mode() {
438 None => {
439 // If the choice mode is none, then the value can not be set by
440 // the select and/or imply methods, therefore, it is only verified
441 // against it's default value. If it is the same as the default
442 // value, the value is to be reset, otherwise it is to be set.
443 if is_current {
444 if let Some(value) = config_item.config_value() {
445 if value != default_value {
446 config_item.set_value(value);
447 } else {
448 config_item.reset_value();
449 }
450 } else {
451 config_item.reset_value();
452 }
453 config_item.reset_config_value()
454 } else {
455 config_item.reset_all()
456 }
457 }
458 Some(_) => {
459 if is_current {
460 let maybe_selects_value =
461 Self::get_highest_reverse_dependency_value(
462 &evaluator,
463 &selects_me,
464 );
465 let maybe_implies_value =
466 Self::get_highest_reverse_dependency_value(
467 &evaluator, &imply_me,
468 );
469
470 // If a value is selected, then the selected value is the minimum value
471 // which can be used by the item. Thus, if the value of the selecting
472 // object is m, then the minimum value of the selected value is also m,
473 // any value lower than m, is in error.
474
475 // If a value is implied, then the same rules apply, just only to the
476 // default value. The value can however be overridden completely.
477
478 match (maybe_selects_value, maybe_implies_value) {
479 (Some(selects_value), _) => {
480 // Though the item is selected, the value is that of the implication,
481 // if the implication is higher.
482 config_item.set_choice_mode(
483 true,
484 selects_value.clone().into(),
485 );
486
487 // if the config value > the select value, then retain the
488 // choice value, otherwise, remove it.
489 if let Some(config_value) = config_item.config_value() {
490 if config_value < selects_value {
491 config_item.reset_value();
492 } else {
493 let default_value = evaluator
494 .config_item_default_value(config_item);
495 if config_value != default_value {
496 config_item.set_value(config_value);
497 } else {
498 config_item.reset_value();
499 }
500 }
501 }
502 config_item.reset_config_value();
503 }
504 (_, Some(implies_value)) => {
505 // If a value is explicitly set, and is not equal to the lower bound nor the
506 // default value, then set the value explicitly.
507 let default_value =
508 evaluator.config_item_default_value(config_item);
509
510 config_item.set_choice_mode(
511 false,
512 implies_value.clone().into(),
513 );
514
515 if let Some(config_value) = config_item.config_value() {
516 if config_value != default_value
517 && config_value != implies_value
518 {
519 config_item.set_value(config_value);
520 } else {
521 config_item.reset_value();
522 }
523 }
524 config_item.reset_config_value();
525 }
526 (_, None) => {
527 let default_value =
528 evaluator.config_item_default_value(config_item);
529 config_item.set_choice_mode(false, Tristate::FALSE);
530 if let Some(config_value) = config_item.config_value() {
531 if config_value != default_value {
532 config_item.set_value(config_value);
533 } else {
534 config_item.reset_value();
535 }
536 }
537 config_item.reset_config_value();
538 }
539 }
540 } else {
541 config_item.reset_all()
542 }
543 }
544 }
545 }
546 }
547 }
548 }
549 }
550
551 fn take_available_snapshot(
552 snapshots: &HashMap<ItemReference, HashSet<ItemReference>>,
553 ) -> Option<ItemReference> {
554 let keys: HashSet<ItemReference> = snapshots.keys().cloned().collect();
555 for (snapshot_itemref, snapshot_dependencies) in snapshots {
556 let mut resolved = true;
557 for dep in snapshot_dependencies {
558 if keys.contains(dep) {
559 resolved = false;
560 break;
561 }
562 }
563 if resolved {
564 return Some(snapshot_itemref.clone());
565 }
566 }
567 None
568 }
569
570 fn get_highest_reverse_dependency_value(
571 evaluator: &ExpensiveEvaluator,
572 revdeps: &HashMap<String, Option<ResolvedExpr>>,
573 ) -> Option<Variant> {
574 let itemrefs = evaluator.dependency_itemrefs(revdeps);
575
576 if itemrefs.len() > 0 {
577 let mut highest_value = Variant::from(false);
578 for itemref in itemrefs {
579 let value = evaluator.current_value(&itemref);
580 if let Some(value) = value {
581 if value > highest_value {
582 highest_value = value;
583 }
584 }
585 }
586 Some(highest_value)
587 } else {
588 None
589 }
590 }
591
592 /// Writes the current configuration known to the configuration registry
593 /// into a file, typically this file has the file name ".config"
594 pub fn write_dotconfig_file(&self, dotconfig_filename: &str) -> Result<(), Error> {
595 let mut f = OpenOptions::new()
596 .read(true)
597 .write(true)
598 .create(true)
599 .truncate(true)
600 .open(dotconfig_filename)?;
601 f.write("#\n".as_bytes())?;
602 f.write("# Automatically generated file; DO NOT EDIT\n".as_bytes())?;
603 match self.write_dotconfig(&mut f) {
604 Ok(_) => Ok(()),
605 Err(e) => Err(Error::from(format!(
606 "Saving configuration to: {} failed - {}",
607 dotconfig_filename, e
608 ))),
609 }
610 }
611
612 /// Writes the current configuration known to the configuration registry
613 /// into a Write trait supporting type.
614 pub fn write_dotconfig(&self, output: &mut dyn Write) -> Result<(), Error> {
615 output.write("# ".as_bytes())?;
616 output.write(self.main_menu_name.to_string().as_bytes())?;
617 output.write("\n#\n\n".as_bytes())?;
618 for (itemref, _) in self.items() {
619 match itemref {
620 ItemReference::Config(name) | ItemReference::MenuConfig(name) => {
621 match self.current_value(itemref) {
622 Some(value) => {
623 output.write(name.to_string().as_bytes())?;
624 output.write("=".as_bytes())?;
625 output.write(value.dot_config().as_bytes())?;
626 }
627 None => {
628 output.write("#".as_bytes())?;
629 output.write(name.as_bytes())?;
630 output.write("=is not set".as_bytes())?;
631 }
632 };
633 output.write("\n".as_bytes())?;
634 }
635 _ => (),
636 }
637 }
638
639 Ok(())
640 }
641
642 /// Reads the saved configuration from a file, typically this file has
643 /// the file name ".config", and mutates the configuration registry
644 /// accordingly.
645 pub fn read_dotconfig_file(&mut self, dotconfig_filename: &str) -> Result<(), LoadError> {
646 let mut f = match OpenOptions::new().read(true).open(dotconfig_filename) {
647 Ok(f) => f,
648 Err(e) => {
649 return Err(LoadError::from(Error::from(format!(
650 "⛔ Can not read {} - {}",
651 dotconfig_filename, e
652 ))));
653 }
654 };
655
656 self.read_dotconfig(&mut f)
657 }
658
659 /// Reads the saved configuration from a Read trait supporting type,
660 /// and mutates the configuration registry accordingly.
661 pub fn read_dotconfig(&mut self, input: &mut dyn Read) -> Result<(), LoadError> {
662 let reader = BufReader::new(input);
663 let lines = reader.lines();
664 for (_, item) in self.items.iter_mut() {
665 for (_, config_item) in item.config_items_mut() {
666 config_item.reset_value();
667 }
668 }
669 let result = self.read_dotconfig_by_line(lines);
670 result
671 }
672
673 fn read_dotconfig_by_line(
674 &mut self,
675 lines: Lines<BufReader<&mut dyn Read>>,
676 ) -> Result<(), LoadError> {
677 let mut invalid_lines: Vec<(String, String)> = Vec::new();
678
679 for line in lines {
680 // Get the actual content of the line or return an error
681 let line = line?;
682
683 // Trim the line such that it begins with a valid character
684 let line = line.trim();
685
686 // If the line is empty, skip it
687 if line.is_empty() {
688 continue;
689 }
690
691 // If the line starts with a pund/hash symbol, it is a comment
692 // skip it.
693 if line.starts_with('#') {
694 continue;
695 }
696
697 // LHS receives the name of the descriptor and RHS receives
698 // the actual value.
699 let mut lhs = String::new();
700 let mut rhs = String::new();
701 let mut invalid = false;
702 let mut finished = false;
703 let mut in_string = false;
704 let mut escape = false;
705
706 // Altough reading a line might fail, the process will continue
707 // and simply skip the current value.
708 #[derive(Debug)]
709 enum MachineState {
710 Start,
711 Lhs,
712 Assign,
713 StartRhs,
714 Rhs,
715 End,
716 }
717
718 let mut state = MachineState::Start;
719 for c in line.chars() {
720 if !invalid && !finished {
721 match state {
722 MachineState::Start => {
723 if !c.is_whitespace() && c != '=' && c != '#' {
724 state = MachineState::Lhs;
725 lhs.push(c);
726 } else if c.is_whitespace() {
727 // Skip the current value
728 } else {
729 // This line is illegal
730 invalid = true;
731 }
732 }
733 MachineState::Lhs => {
734 if !c.is_whitespace() && c != '=' {
735 lhs.push(c);
736 } else if c.is_whitespace() {
737 state = MachineState::Assign;
738 } else if c == '=' {
739 state = MachineState::StartRhs;
740 } else {
741 invalid = true;
742 }
743 }
744 MachineState::Assign => {
745 if c.is_whitespace() {
746 // Skip the current value
747 } else if c == '=' {
748 state = MachineState::StartRhs;
749 } else {
750 invalid = true;
751 }
752 }
753 MachineState::StartRhs => {
754 if !c.is_whitespace() && c != '#' {
755 state = MachineState::Rhs;
756 if c == '"' {
757 in_string = true;
758 }
759 rhs.push(c);
760 } else if c.is_whitespace() {
761 // Skip the current value
762 } else {
763 // The value is empty
764 finished = true;
765 }
766 }
767 MachineState::Rhs => {
768 if c == '"' {
769 if in_string && !escape {
770 in_string = false;
771 rhs.push(c);
772 } else if in_string && escape {
773 rhs.push(c);
774 } else if !in_string {
775 in_string = true;
776 rhs.push(c);
777 } else {
778 invalid = true;
779 }
780 } else if c == '\\' {
781 if escape && in_string {
782 rhs.push(c);
783 escape = false;
784 } else if in_string {
785 escape = true;
786 } else {
787 rhs.push(c);
788 }
789 } else if !c.is_whitespace() && !in_string && c != '#' || in_string {
790 rhs.push(c);
791 } else if c.is_whitespace() {
792 state = MachineState::End;
793 } else if c == '#' {
794 finished = true;
795 } else {
796 invalid = true;
797 }
798 }
799 MachineState::End => {
800 if c.is_whitespace() {
801 // Ignore
802 } else if c == '#' {
803 finished = true;
804 } else {
805 invalid = true;
806 }
807 }
808 }
809 }
810 }
811
812 if !escape && !invalid {
813 let value = Variant::from_dot_config(&rhs);
814 match self.find_itemref(&lhs) {
815 Some(itemref) => match self.int_set_value(&itemref, value) {
816 Ok(_) => (),
817 Err(e) => invalid_lines.push((e.to_string(), line.to_string())),
818 },
819 None => {
820 invalid_lines
821 .push((format!("Invalid or unknown item: {lhs}"), line.to_string()));
822 }
823 }
824 } else {
825 invalid_lines.push(("Invalid syntax".to_owned(), line.to_string()));
826 }
827 }
828
829 self.reset()?;
830
831 match invalid_lines.is_empty() {
832 true => Ok(()),
833 false => Err(LoadError::from(Error::from(invalid_lines))),
834 }
835 }
836}