From 50c95154dd55419e43fc6ce6f54be47782a65917 Mon Sep 17 00:00:00 2001 From: peefy Date: Tue, 30 Jul 2024 13:08:09 +0800 Subject: [PATCH] feat: override union and insert config Signed-off-by: peefy --- kclvm/ast/src/path.rs | 16 ++ kclvm/query/src/override.rs | 397 +++++++++++++++++++++++++++--------- kclvm/query/src/tests.rs | 13 +- 3 files changed, 327 insertions(+), 99 deletions(-) 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..b080e5cf2 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; @@ -297,6 +297,85 @@ fn apply_import_paths_on_module(m: &mut ast::Module, import_paths: &[String]) -> Ok(()) } +macro_rules! override_top_level_stmt { + ($self:expr, $stmt: expr) => { + let item = $stmt.value.clone(); + let mut value = $self.clone_override_value(); + // Use position information that needs to override the expression. + value.set_pos(item.pos()); + match &$self.operation { + ast::ConfigEntryOperation::Union => { + if let ast::Expr::Config(insert_config_expr) = &value.node { + match &mut $stmt.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()); + $self.has_override = true; + } else { + if replace_config_with_path_parts( + config_expr, + &parts, + &$self.action, + &item.node.operation, + &Some(item.node.value.clone()), + ) { + $self.has_override = 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()); + $self.has_override = true; + } else { + if replace_config_with_path_parts( + config_expr, + &parts, + &$self.action, + &item.node.operation, + &Some(item.node.value.clone()), + ) { + $self.has_override = true; + } + } + } + } + _ => {} + } + } else { + // Override the node value. + $stmt.value = value; + $self.has_override = true; + } + } + ast::ConfigEntryOperation::Insert => { + if let ast::Expr::List(insert_list_expr) = &value.node { + if let ast::Expr::List(list_expr) = &mut $stmt.value.node { + for value in &insert_list_expr.elts { + list_expr.elts.push(value.clone()); + } + $self.has_override = true; + } + } + } + ast::ConfigEntryOperation::Override => { + // Override the node value. + $stmt.value = value; + $self.has_override = true; + } + } + }; +} + /// OverrideTransformer is used to walk AST and transform it with the override values. struct OverrideTransformer { pub target_id: String, @@ -326,18 +405,18 @@ impl<'ctx> MutSelfMutWalker<'ctx> for OverrideTransformer { let target = assign_stmt.targets.get(0).unwrap().node.clone(); let target = get_target_path(&target); if target == self.target_id { - let item = assign_stmt.value.clone(); - - let mut value = self.clone_override_value(); - // Use position information that needs to override the expression. - value.set_pos(item.pos()); - // Override the node value. - assign_stmt.value = value; - self.has_override = true; + override_top_level_stmt!(self, assign_stmt); } } - } - if let ast::Stmt::Unification(unification_stmt) = &mut stmt.node { + } else if let ast::Stmt::AugAssign(aug_assign_stmt) = &mut stmt.node { + if self.field_paths.len() == 0 { + let target = aug_assign_stmt.target.node.clone(); + let target = get_target_path(&target); + if target == self.target_id { + override_top_level_stmt!(self, aug_assign_stmt); + } + } + } else if let ast::Stmt::Unification(unification_stmt) = &mut stmt.node { let target = match unification_stmt.target.node.names.get(0) { Some(name) => name, None => bug!( @@ -347,17 +426,55 @@ impl<'ctx> MutSelfMutWalker<'ctx> for OverrideTransformer { }; if target.node == self.target_id { let item = unification_stmt.value.clone(); - let mut value = self.clone_override_value(); // Use position information that needs to override the expression. value.set_pos(item.pos()); - - // Unification is only support to override the schema expression. - if let ast::Expr::Schema(schema_expr) = value.node { - if self.field_paths.len() == 0 { - self.has_override = true; - unification_stmt.value = - Box::new(ast::Node::dummy_node(schema_expr)); + match &self.operation { + ast::ConfigEntryOperation::Union => { + if let ast::Expr::Config(insert_config_expr) = &value.node { + if let ast::Expr::Config(config_expr) = + &mut unification_stmt.value.node.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()); + self.has_override = true; + } else { + if replace_config_with_path_parts( + config_expr, + &parts, + &self.action, + &item.node.operation, + &Some(item.node.value.clone()), + ) { + self.has_override = true; + } + } + } + } + } else { + // Unification is only support to override the schema expression. + if let ast::Expr::Schema(schema_expr) = value.node { + if self.field_paths.len() == 0 { + unification_stmt.value = + Box::new(ast::Node::dummy_node(schema_expr)); + self.has_override = true; + } + } + } + } + ast::ConfigEntryOperation::Insert + | ast::ConfigEntryOperation::Override => { + // Unification is only support to override the schema expression. + if let ast::Expr::Schema(schema_expr) = value.node { + if self.field_paths.len() == 0 { + unification_stmt.value = + Box::new(ast::Node::dummy_node(schema_expr)); + self.has_override = true; + } + } } } } @@ -604,81 +721,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; + } + } } } + 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 = self.replace_config_with_path_parts(config_expr, &parts[1..]); - } + } + // 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 +905,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; } diff --git a/kclvm/query/src/tests.rs b/kclvm/query/src/tests.rs index c7ecd0350..a6647f6e8 100644 --- a/kclvm/query/src/tests.rs +++ b/kclvm/query/src/tests.rs @@ -114,12 +114,18 @@ fn test_override_file_config() { "appConfigurationUnification.overQuota=False".to_string(), "appConfigurationUnification.resource.cpu-".to_string(), "appConfigurationUnification.svc=s.Service {}".to_string(), - "config.x:1".to_string(), + "appConfigurationUnification:{name=\"name\"}".to_string(), + "config.x:{a:1}".to_string(), + "config.x:{b:2}".to_string(), + "config.x:{b:3}".to_string(), + "config.x:{c.d:4}".to_string(), "config.y=1".to_string(), "config.z+=[1,2,3]".to_string(), + "config.z+=[4,5,6]".to_string(), "var1:1".to_string(), "var2=1".to_string(), "var3+=[1,2,3]".to_string(), + "var3+=[4,5,6]".to_string(), "var4:AppConfiguration {image:'image'}".to_string(), ]; let import_paths = vec!["service as s".to_string()]; @@ -187,11 +193,12 @@ appConfigurationUnification: AppConfiguration { mainContainer: Main {name: "override_name"} overQuota: False svc = s.Service {} + name = "name" } -config = {x: 1, y = 1, z += [1, 2, 3]} +config = {x: {a: 1, b: 3, c: {d: 4}}, y = 1, z += [1, 2, 3, 4, 5, 6]} var1 = 1 var2 = 1 -var3 += [1, 2, 3] +var3 += [1, 2, 3, 4, 5, 6] var4: AppConfiguration {image: 'image'} "# );