Skip to content

Commit c3ee590

Browse files
Copilotguoweikang
andauthored
xconfig: Add shell expression support and fix choice default selection (#13)
* Initial plan * Add shell expression support to xconfig parser Co-authored-by: guoweikang <18571063+guoweikang@users.noreply.github.com> * Add Choice default initialization and tests Co-authored-by: guoweikang <18571063+guoweikang@users.noreply.github.com> * Fix saveconfig to handle all entry types and shell expressions Co-authored-by: guoweikang <18571063+guoweikang@users.noreply.github.com> * Address code review feedback: fix string escape handling and remove meaningless assertions Co-authored-by: guoweikang <18571063+guoweikang@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: guoweikang <18571063+guoweikang@users.noreply.github.com>
1 parent c7dd15c commit c3ee590

10 files changed

Lines changed: 492 additions & 33 deletions

File tree

xtask/xconfig/src/cli/menuconfig.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ fn extract_symbols_from_entries(entries: &[crate::kconfig::ast::Entry], symbol_t
7171
if let Some(default_expr) = &config.properties.default {
7272
if let crate::kconfig::Expr::Const(val) = default_expr {
7373
symbol_table.set_value(&config.name, val.clone());
74+
} else if let crate::kconfig::Expr::ShellExpr(shell_expr) = default_expr {
75+
// Evaluate shell expression for default
76+
if let Ok(value) = crate::kconfig::shell_expr::evaluate_shell_expr(shell_expr, symbol_table) {
77+
if !value.is_empty() {
78+
symbol_table.set_value(&config.name, value);
79+
}
80+
}
7481
}
7582
}
7683
}
@@ -81,6 +88,14 @@ fn extract_symbols_from_entries(entries: &[crate::kconfig::ast::Entry], symbol_t
8188
for option in &choice.options {
8289
symbol_table.add_symbol(option.name.clone(), option.symbol_type.clone());
8390
}
91+
92+
// Apply choice default if specified
93+
if let Some(default_name) = &choice.default {
94+
symbol_table.set_value(default_name, "y".to_string());
95+
} else if let Some(first_option) = choice.options.first() {
96+
// No default specified, select first option (standard Kconfig behavior)
97+
symbol_table.set_value(&first_option.name, "y".to_string());
98+
}
8499
}
85100
Entry::Menu(menu) => {
86101
extract_symbols_from_entries(&menu.entries, symbol_table);

xtask/xconfig/src/cli/saveconfig.rs

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,7 @@ pub fn saveconfig_command(
2020
let mut symbols = crate::kconfig::SymbolTable::new();
2121

2222
// Extract symbols from AST and apply defaults
23-
for entry in &ast.entries {
24-
if let crate::kconfig::ast::Entry::Config(config) = entry {
25-
// Strip CONFIG_ prefix if present
26-
let clean_name = config.name.strip_prefix("CONFIG_").unwrap_or(&config.name);
27-
symbols.add_symbol(clean_name.to_string(), config.symbol_type.clone());
28-
29-
// Apply default value if present
30-
// Note: This is a simplified implementation that only handles simple
31-
// constant and symbol expressions. Full expression evaluation
32-
// (with dependencies and conditional defaults) is not yet implemented.
33-
if let Some(default_expr) = &config.properties.default {
34-
// Extract value from simple expressions
35-
if let crate::kconfig::ast::Expr::Const(val) = default_expr {
36-
symbols.set_value(clean_name, val.clone());
37-
} else if let crate::kconfig::ast::Expr::Symbol(sym) = default_expr {
38-
// Handle default values like 'y' or 'n'
39-
symbols.set_value(clean_name, sym.clone());
40-
}
41-
// Complex expressions (e.g., conditional defaults with 'if')
42-
// would require full expression evaluation and are not handled here
43-
} else {
44-
// Set to 'n' if no default
45-
match config.symbol_type {
46-
crate::kconfig::ast::SymbolType::Bool |
47-
crate::kconfig::ast::SymbolType::Tristate => {
48-
symbols.set_value(clean_name, "n".to_string());
49-
}
50-
_ => {}
51-
}
52-
}
53-
}
54-
}
23+
extract_symbols_from_entries(&ast.entries, &mut symbols);
5524

5625
// Write .config file
5726
ConfigWriter::write(&output, &symbols)?;
@@ -69,3 +38,71 @@ pub fn saveconfig_command(
6938

7039
Ok(())
7140
}
41+
42+
fn extract_symbols_from_entries(entries: &[crate::kconfig::ast::Entry], symbols: &mut crate::kconfig::SymbolTable) {
43+
use crate::kconfig::ast::Entry;
44+
use crate::kconfig::Expr;
45+
46+
for entry in entries {
47+
match entry {
48+
Entry::Config(config) => {
49+
// Strip CONFIG_ prefix if present
50+
let clean_name = config.name.strip_prefix("CONFIG_").unwrap_or(&config.name);
51+
symbols.add_symbol(clean_name.to_string(), config.symbol_type.clone());
52+
53+
// Apply default value if present
54+
if let Some(default_expr) = &config.properties.default {
55+
if let Expr::Const(val) = default_expr {
56+
symbols.set_value(clean_name, val.clone());
57+
} else if let Expr::Symbol(sym) = default_expr {
58+
// Handle default values like 'y' or 'n'
59+
symbols.set_value(clean_name, sym.clone());
60+
} else if let Expr::ShellExpr(shell_expr) = default_expr {
61+
// Evaluate shell expression
62+
if let Ok(value) = crate::kconfig::shell_expr::evaluate_shell_expr(shell_expr, symbols) {
63+
if !value.is_empty() {
64+
symbols.set_value(clean_name, value);
65+
}
66+
}
67+
}
68+
} else {
69+
// Set to 'n' if no default
70+
match config.symbol_type {
71+
crate::kconfig::ast::SymbolType::Bool |
72+
crate::kconfig::ast::SymbolType::Tristate => {
73+
symbols.set_value(clean_name, "n".to_string());
74+
}
75+
_ => {}
76+
}
77+
}
78+
}
79+
Entry::MenuConfig(menuconfig) => {
80+
let clean_name = menuconfig.name.strip_prefix("CONFIG_").unwrap_or(&menuconfig.name);
81+
symbols.add_symbol(clean_name.to_string(), menuconfig.symbol_type.clone());
82+
}
83+
Entry::Choice(choice) => {
84+
for option in &choice.options {
85+
let clean_name = option.name.strip_prefix("CONFIG_").unwrap_or(&option.name);
86+
symbols.add_symbol(clean_name.to_string(), option.symbol_type.clone());
87+
}
88+
89+
// Apply choice default if specified
90+
if let Some(default_name) = &choice.default {
91+
let clean_default = default_name.strip_prefix("CONFIG_").unwrap_or(default_name);
92+
symbols.set_value(clean_default, "y".to_string());
93+
} else if let Some(first_option) = choice.options.first() {
94+
// No default specified, select first option (standard Kconfig behavior)
95+
let clean_name = first_option.name.strip_prefix("CONFIG_").unwrap_or(&first_option.name);
96+
symbols.set_value(clean_name, "y".to_string());
97+
}
98+
}
99+
Entry::Menu(menu) => {
100+
extract_symbols_from_entries(&menu.entries, symbols);
101+
}
102+
Entry::If(if_entry) => {
103+
extract_symbols_from_entries(&if_entry.entries, symbols);
104+
}
105+
_ => {}
106+
}
107+
}
108+
}

xtask/xconfig/src/kconfig/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub enum SymbolType {
1313
pub enum Expr {
1414
Symbol(String),
1515
Const(String),
16+
ShellExpr(String),
1617
Not(Box<Expr>),
1718
And(Box<Expr>, Box<Expr>),
1819
Or(Box<Expr>, Box<Expr>),

xtask/xconfig/src/kconfig/expr.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::error::{KconfigError, Result};
22
use crate::kconfig::ast::Expr;
33
use crate::kconfig::symbol::SymbolTable;
4+
use crate::kconfig::shell_expr::evaluate_shell_expr;
45

56
pub fn evaluate_expr(expr: &Expr, symbols: &SymbolTable) -> Result<bool> {
67
match expr {
@@ -12,6 +13,11 @@ pub fn evaluate_expr(expr: &Expr, symbols: &SymbolTable) -> Result<bool> {
1213
// "y", "m", "n" constants
1314
Ok(val == "y" || val == "m")
1415
}
16+
Expr::ShellExpr(shell_expr) => {
17+
// Evaluate shell expression and check if result is truthy
18+
let result = evaluate_shell_expr(shell_expr, symbols)?;
19+
Ok(!result.is_empty() && result != "n" && result != "0")
20+
}
1521
Expr::Not(inner) => Ok(!evaluate_expr(inner, symbols)?),
1622
Expr::And(left, right) => {
1723
Ok(evaluate_expr(left, symbols)? && evaluate_expr(right, symbols)?)
@@ -56,6 +62,7 @@ fn get_expr_value(expr: &Expr, symbols: &SymbolTable) -> Result<String> {
5662
match expr {
5763
Expr::Symbol(name) => Ok(symbols.get_value(name).unwrap_or_else(|| "n".to_string())),
5864
Expr::Const(val) => Ok(val.clone()),
65+
Expr::ShellExpr(shell_expr) => evaluate_shell_expr(shell_expr, symbols),
5966
_ => Err(KconfigError::InvalidExpression(
6067
"Complex expression in comparison".to_string(),
6168
)),

xtask/xconfig/src/kconfig/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ pub mod ast;
22
pub mod expr;
33
pub mod lexer;
44
pub mod parser;
5+
pub mod shell_expr;
56
pub mod symbol;
67

78
pub use ast::*;
89
pub use expr::*;
910
pub use lexer::*;
1011
pub use parser::*;
12+
pub use shell_expr::*;
1113
pub use symbol::*;

xtask/xconfig/src/kconfig/parser.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,12 @@ impl Parser {
606606
Token::StringLit(val) => {
607607
let val = val.clone();
608608
self.advance()?;
609-
Ok(Expr::Const(val))
609+
// Check if it contains shell expressions
610+
if val.contains("$(") {
611+
Ok(Expr::ShellExpr(val))
612+
} else {
613+
Ok(Expr::Const(val))
614+
}
610615
}
611616
Token::Number(n) => {
612617
let n = *n;

0 commit comments

Comments
 (0)