Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(v0.10.0-alpha.1): support multiple assign target including member access a.b and index a[b] #1512

Merged
merged 3 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions kclvm/api/src/service/capi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ pub(crate) fn kclvm_get_service_fn_ptr_by_name(name: &str) -> u64 {
"KclvmService.ListOptions" => list_options as *const () as u64,
"KclvmService.ListVariables" => list_variables as *const () as u64,
"KclvmService.ExecProgram" => exec_program as *const () as u64,
#[cfg(feature = "llvm")]
"KclvmService.BuildProgram" => build_program as *const () as u64,
#[cfg(feature = "llvm")]
"KclvmService.ExecArtifact" => exec_artifact as *const () as u64,
"KclvmService.OverrideFile" => override_file as *const () as u64,
"KclvmService.GetSchemaTypeMapping" => get_schema_type_mapping as *const () as u64,
Expand Down Expand Up @@ -373,6 +375,7 @@ pub(crate) fn exec_program(
///
/// result: [*const c_char]
/// Result of the call serialized as protobuf byte sequence
#[cfg(feature = "llvm")]
pub(crate) fn build_program(
serv: *mut kclvm_service,
args: *const c_char,
Expand All @@ -397,6 +400,7 @@ pub(crate) fn build_program(
///
/// result: [*const c_char]
/// Result of the call serialized as protobuf byte sequence
#[cfg(feature = "llvm")]
pub(crate) fn exec_artifact(
serv: *mut kclvm_service,
args: *const c_char,
Expand Down
2 changes: 2 additions & 0 deletions kclvm/api/src/service/jsonrpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ fn register_kclvm_service(io: &mut IoHandler) {
};
futures::future::ready(catch!(kclvm_service_impl, args, exec_program))
});
#[cfg(feature = "llvm")]
io.add_method("KclvmService.BuildProgram", |params: Params| {
let kclvm_service_impl = KclvmServiceImpl::default();
let args: BuildProgramArgs = match params.parse() {
Expand All @@ -131,6 +132,7 @@ fn register_kclvm_service(io: &mut IoHandler) {
};
futures::future::ready(catch!(kclvm_service_impl, args, build_program))
});
#[cfg(feature = "llvm")]
io.add_method("KclvmService.ExecArtifact", |params: Params| {
let kclvm_service_impl = KclvmServiceImpl::default();
let args: ExecArtifactArgs = match params.parse() {
Expand Down
6 changes: 5 additions & 1 deletion kclvm/api/src/service/service_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ use kclvm_query::query::get_full_schema_type;
use kclvm_query::query::CompilationOptions;
use kclvm_query::selector::{list_variables, ListOptions};
use kclvm_query::GetSchemaOption;
use kclvm_runner::{build_program, exec_artifact, exec_program};
use kclvm_runner::exec_program;
#[cfg(feature = "llvm")]
use kclvm_runner::{build_program, exec_artifact};
use kclvm_sema::core::global_state::GlobalState;
use kclvm_sema::resolver::scope::KCLScopeCache;
use kclvm_sema::resolver::Options;
Expand Down Expand Up @@ -507,6 +509,7 @@ impl KclvmServiceImpl {
/// }).unwrap();
/// assert!(!artifact.path.is_empty());
/// ```
#[cfg(feature = "llvm")]
pub fn build_program(&self, args: &BuildProgramArgs) -> anyhow::Result<BuildProgramResult> {
let exec_args = transform_exec_para(&args.exec_args, self.plugin_agent)?;
let artifact = build_program(
Expand Down Expand Up @@ -548,6 +551,7 @@ impl KclvmServiceImpl {
/// assert_eq!(exec_result.err_message, "");
/// assert_eq!(exec_result.yaml_result, "alice:\n age: 18");
/// ```
#[cfg(feature = "llvm")]
pub fn exec_artifact(&self, args: &ExecArtifactArgs) -> anyhow::Result<ExecProgramResult> {
let exec_args = transform_exec_para(&args.exec_args, self.plugin_agent)?;
let result = exec_artifact(&args.path, &exec_args)?;
Expand Down
2 changes: 1 addition & 1 deletion kclvm/api/src/testdata/parse-file.response.json

Large diffs are not rendered by default.

94 changes: 78 additions & 16 deletions kclvm/ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ impl From<Pos> for Range {
}
}

/// The unique index of AST, used for KCL semantic analysis to record AST
/// node semantic information.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct AstIndex(uuid::Uuid);

Expand Down Expand Up @@ -487,21 +489,28 @@ pub struct UnificationStmt {
/// AssignStmt represents an assignment, e.g.
/// ```kcl
/// a: int = 1
/// a["key"] = "value"
/// a.b["key"].c = "value"
/// a[0] = 1
/// ```
/// Valid left-hand side of an assignment expressions:
/// - Expr::Identifier a
/// - Expr::Subscript e.g. `a[0]`, `b["k"]`
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct AssignStmt {
pub targets: Vec<NodeRef<Identifier>>,
pub targets: Vec<NodeRef<Target>>,
pub value: NodeRef<Expr>,
pub ty: Option<NodeRef<Type>>,
}

/// AugAssignStmt represents an argument assignment, e.g.
/// ```kcl
/// a += 1
/// a[0] += 2
/// ```
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct AugAssignStmt {
pub target: NodeRef<Identifier>,
pub target: NodeRef<Target>,
pub value: NodeRef<Expr>,
pub op: AugOp,
}
Expand Down Expand Up @@ -606,24 +615,20 @@ impl SchemaStmt {
}
Stmt::Assign(assign_stmt) => {
for target in &assign_stmt.targets {
if !target.node.names.is_empty() {
attr_list.push((
target.line,
target.column,
target.node.names[0].node.to_string(),
));
}
}
}
Stmt::AugAssign(aug_assign_stmt) => {
if !aug_assign_stmt.target.node.names.is_empty() {
attr_list.push((
aug_assign_stmt.target.line,
aug_assign_stmt.target.column,
aug_assign_stmt.target.node.names[0].node.to_string(),
target.line,
target.column,
target.node.name.node.to_string(),
));
}
}
Stmt::AugAssign(aug_assign_stmt) => {
attr_list.push((
aug_assign_stmt.target.line,
aug_assign_stmt.target.column,
aug_assign_stmt.target.node.name.node.to_string(),
));
}
Stmt::If(if_stmt) => {
loop_body(&if_stmt.body, attr_list);
loop_body(&if_stmt.orelse, attr_list);
Expand Down Expand Up @@ -715,6 +720,7 @@ pub struct RuleStmt {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(tag = "type")]
pub enum Expr {
Target(Target),
Identifier(Identifier),
Unary(UnaryExpr),
Binary(BinaryExpr),
Expand Down Expand Up @@ -750,6 +756,7 @@ pub enum Expr {
impl Expr {
pub fn get_expr_name(&self) -> String {
match self {
Expr::Target(_) => "TargetExpression",
Expr::Identifier(_) => "IdentifierExpression",
Expr::Unary(_) => "UnaryExpression",
Expr::Binary(_) => "BinaryExpression",
Expand Down Expand Up @@ -784,6 +791,46 @@ impl Expr {
}
}

/// Target, e.g.
/// ```kcl
/// a.b.c
/// b
/// _c
/// a["b"][0].c
/// ```
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Target {
pub name: Node<String>,
pub paths: Vec<MemberOrIndex>,
pub pkgpath: String,
}

impl Target {
#[inline]
pub fn get_name(&self) -> &str {
self.name.node.as_str()
}
}

/// Member or index expression
/// - `a.<member>`
/// - `b[<index>]`
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum MemberOrIndex {
Member(NodeRef<String>),
Index(NodeRef<Expr>),
}

impl MemberOrIndex {
#[inline]
pub fn id(&self) -> AstIndex {
match self {
MemberOrIndex::Member(member) => member.id.clone(),
MemberOrIndex::Index(index) => index.id.clone(),
}
}
}

/// Identifier, e.g.
/// ```kcl
/// a
Expand All @@ -799,10 +846,12 @@ pub struct Identifier {
}

impl Identifier {
#[inline]
pub fn get_name(&self) -> String {
self.get_names().join(".")
}

#[inline]
pub fn get_names(&self) -> Vec<String> {
self.names
.iter()
Expand Down Expand Up @@ -1260,6 +1309,19 @@ pub struct NumberLit {
pub value: NumberLitValue,
}

impl ToString for NumberLit {
fn to_string(&self) -> String {
let mut result = match self.value {
NumberLitValue::Int(v) => v.to_string(),
NumberLitValue::Float(v) => v.to_string(),
};
if let Some(suffix) = &self.binary_suffix {
result.push_str(&suffix.value());
}
result
}
}

/// StringLit, e.g.
/// ```kcl
/// "string literal"
Expand Down
54 changes: 54 additions & 0 deletions kclvm/ast/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,60 @@ pub fn get_key_path(key: &Option<ast::NodeRef<ast::Expr>>) -> String {
}
}

/// 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.
///
/// # Examples
///
/// ```
/// use kclvm_ast::ast;
/// use kclvm_ast::path::get_target_path;
///
/// let target = ast::Target {
/// name: ast::Node::dummy_node("alice".to_string()),
/// paths: vec![],
/// pkgpath: "".to_string(),
/// };
/// assert_eq!(get_target_path(&target), "alice");
/// ```
#[inline]
pub fn get_target_path(key: &ast::Target) -> String {
let mut result = key.name.node.to_string();
for path in &key.paths {
match path {
ast::MemberOrIndex::Member(member) => {
result.push('.');
result.push_str(&member.node);
}
ast::MemberOrIndex::Index(index) => {
result.push('[');
match &index.node {
ast::Expr::Unary(unary_expr) => match &unary_expr.operand.node {
ast::Expr::NumberLit(number) => {
result.push_str(&unary_expr.op.symbol());
result.push_str(&number.to_string());
}
_ => {
result.push_str("...");
}
},
ast::Expr::NumberLit(number) => {
result.push_str(&number.to_string());
}
ast::Expr::StringLit(string_lit) => {
result.push_str(&format!("{:?}", string_lit.value));
}
_ => {
result.push_str("...");
}
}
result.push(']');
}
}
}
result
}

/// Get all attribute paths recursively from a config expression AST node.
///
/// # Examples
Expand Down
35 changes: 17 additions & 18 deletions kclvm/ast/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use crate::{ast, ast::*};

/// Construct an AssignStmt node with assign_value as value
fn build_assign_node(attr_name: &str, assign_value: NodeRef<Expr>) -> NodeRef<Stmt> {
let iden = node_ref!(Identifier {
names: vec![Node::dummy_node(attr_name.to_string())],
pkgpath: String::new(),
ctx: ExprContext::Store
let target = node_ref!(Target {
name: Node::dummy_node(attr_name.to_string()),
paths: vec![],
pkgpath: "".to_string()
});

node_ref!(Stmt::Assign(AssignStmt {
value: assign_value,
targets: vec![iden],
targets: vec![target],
ty: None
}))
}
Expand All @@ -26,10 +26,10 @@ fn get_dummy_assign_ast() -> ast::Node<ast::AssignStmt> {
ast::Node::new(
ast::AssignStmt {
targets: vec![Box::new(ast::Node::new(
ast::Identifier {
names: vec![Node::dummy_node(String::from("a"))],
pkgpath: String::from(filename),
ctx: ast::ExprContext::Load,
ast::Target {
name: Node::dummy_node(String::from("a")),
paths: vec![],
pkgpath: "".to_string(),
},
String::from(filename),
line,
Expand Down Expand Up @@ -68,10 +68,10 @@ fn get_dummy_assign_binary_ast() -> ast::Node<ast::AssignStmt> {
ast::Node::new(
ast::AssignStmt {
targets: vec![Box::new(ast::Node::new(
ast::Identifier {
names: vec![Node::dummy_node(String::from("a"))],
pkgpath: String::from(filename),
ctx: ast::ExprContext::Load,
ast::Target {
name: Node::dummy_node(String::from("a")),
paths: vec![],
pkgpath: "".to_string(),
},
String::from(filename),
line,
Expand Down Expand Up @@ -143,16 +143,15 @@ fn test_ast_print_assign_binary() {
fn test_mut_walker() {
pub struct VarMutSelfMutWalker;
impl<'ctx> MutSelfMutWalker<'ctx> for VarMutSelfMutWalker {
fn walk_identifier(&mut self, identifier: &'ctx mut ast::Identifier) {
if identifier.names[0].node == "a" {
let id_mut = identifier.names.get_mut(0).unwrap();
id_mut.node = "x".to_string();
fn walk_target(&mut self, target: &'ctx mut ast::Target) {
if target.name.node == "a" {
target.name.node = "x".to_string();
}
}
}
let mut assign_stmt = get_dummy_assign_ast();
VarMutSelfMutWalker {}.walk_assign_stmt(&mut assign_stmt.node);
assert_eq!(assign_stmt.node.targets[0].node.names[0].node, "x")
assert_eq!(assign_stmt.node.targets[0].node.name.node, "x")
}

#[test]
Expand Down
Loading
Loading