From 854784390de1aba0d802b2f7bd44632e453aa6b5 Mon Sep 17 00:00:00 2001 From: peefy Date: Tue, 30 Jul 2024 11:52:51 +0800 Subject: [PATCH] feat: impl override union config. --- a.k | 5 + kclvm/ast/src/path.rs | 16 +++ kclvm/query/src/override.rs | 244 ++++++++++++++++++++++++------------ 3 files changed, 187 insertions(+), 78 deletions(-) create mode 100644 a.k diff --git a/a.k b/a.k new file mode 100644 index 000000000..193843b1c --- /dev/null +++ b/a.k @@ -0,0 +1,5 @@ +a = { + b = { + a = 5 + } +} diff --git a/kclvm/ast/src/path.rs b/kclvm/ast/src/path.rs index 617a9468d..4d8b06654 100644 --- a/kclvm/ast/src/path.rs +++ b/kclvm/ast/src/path.rs @@ -34,6 +34,22 @@ pub fn get_key_path(key: &Option>) -> String { } } +/// Get config key parts from the AST key node and convert string-based AST nodes including +/// `ast::Expr::Identifier` and `ast::Expr::StringLit` to strings. +#[inline] +pub fn get_key_parts(key: &Option>) -> Vec<&str> { + match key { + Some(key) => match &key.node { + ast::Expr::Identifier(identifier) => { + identifier.names.iter().map(|v| v.node.as_str()).collect() + } + ast::Expr::StringLit(string_lit) => vec![string_lit.value.as_str()], + _ => vec![], + }, + None => vec![], + } +} + /// Get assign target path from the AST key node and convert string-based AST nodes including /// `ast::Expr::Identifier` and `ast::Expr::StringLit` to strings. /// diff --git a/kclvm/query/src/override.rs b/kclvm/query/src/override.rs index 9dfd0b823..51c63b478 100644 --- a/kclvm/query/src/override.rs +++ b/kclvm/query/src/override.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Result}; use compiler_base_macros::bug; use kclvm_ast::config::try_get_config_expr_mut; -use kclvm_ast::path::get_key_path; +use kclvm_ast::path::{get_key_parts, get_key_path}; use kclvm_ast::walk_list_mut; use kclvm_ast::walker::MutSelfMutWalker; use kclvm_ast::MAIN_PKG; @@ -604,81 +604,178 @@ impl OverrideTransformer { .iter() .map(|s| s.as_str()) .collect::>(); - self.replace_config_with_path_parts(config_expr, &parts) + replace_config_with_path_parts( + config_expr, + &parts, + &self.action, + &self.operation, + &self.override_value, + ) } - /// Replace AST config expr with one part of path. The implementation of this function - /// uses recursive matching to find the config entry need to be modified. - fn replace_config_with_path_parts( - &self, - config_expr: &mut ast::ConfigExpr, - parts: &[&str], - ) -> bool { - // Do not replace empty path parts and out of index parts on the config expression. - if parts.is_empty() { - return false; + /// Clone a override value + #[inline] + fn clone_override_value(&self) -> ast::NodeRef { + match &self.override_value { + Some(v) => v.clone(), + None => bug!("Override value is None"), } - // Always take the first part to match, because recursive search is required. - let part = parts[0]; - let mut delete_index_set = HashSet::new(); - let mut changed = false; - // Loop all entries in the config expression and replace, because there may be duplicate - // configuration items in config. - for (i, item) in config_expr.items.iter_mut().enumerate() { - // Compare each field of the config structure one by one. - // - `part` denotes the path entered by the user to be modified. - // - `get_path_key` returns the real config key name. - // For example, the real config node is `a: {b: c: {}}`. The path - // that needs to be modified is `a.b.c`, and its parts are ["a", "b", "c"]. - if part == get_key_path(&item.node.key) { - // When the last part of the path is successfully recursively matched, - // it indicates that the original value that needs to be overwritten - // is successfully found, and the new value is used to overwrite it. - // - `parts.len() == 1` denotes the path matches exactly. - if parts.len() == 1 { - match self.action { - ast::OverrideAction::CreateOrUpdate => { - let mut value = self.clone_override_value(); + } +} + +/// Replace AST config expr with one part of path. The implementation of this function +/// uses recursive matching to find the config entry need to be modified. +fn replace_config_with_path_parts( + config_expr: &mut ast::ConfigExpr, + parts: &[&str], + action: &ast::OverrideAction, + operation: &ast::ConfigEntryOperation, + value: &Option>, +) -> bool { + // Do not replace empty path parts and out of index parts on the config expression. + if parts.is_empty() { + return false; + } + // Always take the first part to match, because recursive search is required. + let part = parts[0]; + let mut delete_index_set = HashSet::new(); + let mut changed = false; + // Loop all entries in the config expression and replace, because there may be duplicate + // configuration items in config. + for (i, item) in config_expr.items.iter_mut().enumerate() { + // Compare each field of the config structure one by one. + // - `part` denotes the path entered by the user to be modified. + // - `get_path_key` returns the real config key name. + // For example, the real config node is `a: {b: c: {}}`. The path + // that needs to be modified is `a.b.c`, and its parts are ["a", "b", "c"]. + if part == get_key_path(&item.node.key) { + // When the last part of the path is successfully recursively matched, + // it indicates that the original value that needs to be overwritten + // is successfully found, and the new value is used to overwrite it. + // - `parts.len() == 1` denotes the path matches exactly. + if parts.len() == 1 { + match action { + ast::OverrideAction::CreateOrUpdate => { + if let Some(value) = value { + let mut value = value.clone(); // Use position information that needs to override the expression. value.set_pos(item.pos()); - // Override the node value. - item.node.value = value; - changed = true; - } - ast::OverrideAction::Delete => { - // Store the config entry delete index into the delete index set. - // Because we can't delete the entry directly in the loop - delete_index_set.insert(i); - changed = true; + match operation { + ast::ConfigEntryOperation::Union => { + if let ast::Expr::Config(insert_config_expr) = &value.node { + match &mut item.node.value.node { + ast::Expr::Schema(schema_expr) => { + if let ast::Expr::Config(config_expr) = + &mut schema_expr.config.node + { + for item in &insert_config_expr.items { + let parts = get_key_parts(&item.node.key); + // Deal double star and config if expr + if parts.is_empty() { + config_expr.items.push(item.clone()); + changed = true; + } else { + if replace_config_with_path_parts( + config_expr, + &parts, + action, + &item.node.operation, + &Some(item.node.value.clone()), + ) { + changed = true; + } + } + } + } + } + ast::Expr::Config(config_expr) => { + for item in &insert_config_expr.items { + let parts = get_key_parts(&item.node.key); + // Deal double star and config if expr + if parts.is_empty() { + config_expr.items.push(item.clone()); + changed = true; + } else { + if replace_config_with_path_parts( + config_expr, + &parts, + action, + &item.node.operation, + &Some(item.node.value.clone()), + ) { + changed = true; + } + } + } + } + _ => {} + } + } else { + // Override the node value. + item.node.value = value; + changed = true; + } + } + ast::ConfigEntryOperation::Insert => { + if let ast::Expr::List(insert_list_expr) = &value.node { + if let ast::Expr::List(list_expr) = + &mut item.node.value.node + { + for value in &insert_list_expr.elts { + list_expr.elts.push(value.clone()); + } + changed = true; + } + } + } + ast::ConfigEntryOperation::Override => { + // Override the node value. + item.node.value = value; + changed = true; + } + } } } - } - // Replace value recursively using the path composed by subsequent parts. - // - // The reason for using recursion instead of looping for path matching - // is that rust cannot directly hold shared references to AST nodes - // (ast::NodeRef is a Box), so recursive search is performed - // directly on AST nodes. - else if let Some(config_expr) = try_get_config_expr_mut(&mut item.node.value.node) - { - changed = self.replace_config_with_path_parts(config_expr, &parts[1..]); + ast::OverrideAction::Delete => { + // Store the config entry delete index into the delete index set. + // Because we can't delete the entry directly in the loop + delete_index_set.insert(i); + changed = true; + } } } + // Replace value recursively using the path composed by subsequent parts. + // + // The reason for using recursion instead of looping for path matching + // is that rust cannot directly hold shared references to AST nodes + // (ast::NodeRef is a Box), so recursive search is performed + // directly on AST nodes. + else if let Some(config_expr) = try_get_config_expr_mut(&mut item.node.value.node) { + changed = replace_config_with_path_parts( + config_expr, + &parts[1..], + action, + operation, + value, + ); + } } - // Delete entries according delete index set. - if !delete_index_set.is_empty() { - let items: Vec<(usize, &ast::NodeRef)> = config_expr - .items - .iter() - .enumerate() - .filter(|(i, _)| !delete_index_set.contains(i)) - .collect(); - config_expr.items = items - .iter() - .map(|(_, item)| <&ast::NodeRef>::clone(item).clone()) - .collect(); - } else if let ast::OverrideAction::CreateOrUpdate = self.action { - if !changed { + } + // Delete entries according delete index set. + if !delete_index_set.is_empty() { + let items: Vec<(usize, &ast::NodeRef)> = config_expr + .items + .iter() + .enumerate() + .filter(|(i, _)| !delete_index_set.contains(i)) + .collect(); + config_expr.items = items + .iter() + .map(|(_, item)| <&ast::NodeRef>::clone(item).clone()) + .collect(); + } else if let ast::OverrideAction::CreateOrUpdate = action { + if !changed { + if let Some(value) = value { let key = ast::Identifier { names: parts .iter() @@ -691,21 +788,12 @@ impl OverrideTransformer { .items .push(Box::new(ast::Node::dummy_node(ast::ConfigEntry { key: Some(Box::new(ast::Node::dummy_node(ast::Expr::Identifier(key)))), - value: self.clone_override_value(), - operation: self.operation.clone(), + value: value.clone(), + operation: operation.clone(), }))); changed = true; } } - return changed; - } - - /// Clone a override value - #[inline] - fn clone_override_value(&self) -> ast::NodeRef { - match &self.override_value { - Some(v) => v.clone(), - None => bug!("Override value is None"), - } } + return changed; }