Skip to content

Commit

Permalink
feat: impl override union config.
Browse files Browse the repository at this point in the history
  • Loading branch information
Peefy committed Jul 30, 2024
1 parent a589e15 commit 8547843
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 78 deletions.
5 changes: 5 additions & 0 deletions a.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
a = {
b = {
a = 5
}
}
16 changes: 16 additions & 0 deletions kclvm/ast/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ pub fn get_key_path(key: &Option<ast::NodeRef<ast::Expr>>) -> 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<ast::NodeRef<ast::Expr>>) -> 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.
///
Expand Down
244 changes: 166 additions & 78 deletions kclvm/query/src/override.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -604,81 +604,178 @@ impl OverrideTransformer {
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>();
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<ast::Expr> {
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<ast::NodeRef<ast::Expr>>,
) -> 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<T> is a Box<T>), 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<T> is a Box<T>), 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<ast::ConfigEntry>)> = config_expr
.items
.iter()
.enumerate()
.filter(|(i, _)| !delete_index_set.contains(i))
.collect();
config_expr.items = items
.iter()
.map(|(_, item)| <&ast::NodeRef<ast::ConfigEntry>>::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<ast::ConfigEntry>)> = config_expr
.items
.iter()
.enumerate()
.filter(|(i, _)| !delete_index_set.contains(i))
.collect();
config_expr.items = items
.iter()
.map(|(_, item)| <&ast::NodeRef<ast::ConfigEntry>>::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()
Expand All @@ -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<ast::Expr> {
match &self.override_value {
Some(v) => v.clone(),
None => bug!("Override value is None"),
}
}
return changed;
}

0 comments on commit 8547843

Please sign in to comment.