Skip to content

Commit

Permalink
feat: support multiple assign target including member access a.b an…
Browse files Browse the repository at this point in the history
…d index `a[b]`

Signed-off-by: peefy <xpf6677@163.com>
  • Loading branch information
Peefy committed Jul 19, 2024
1 parent 3a583ca commit 9e7c0d2
Show file tree
Hide file tree
Showing 141 changed files with 3,111 additions and 1,813 deletions.
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
7 changes: 7 additions & 0 deletions kclvm/ast/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,28 +313,35 @@ impl From<Token> for String {
}

impl Token {
/// New a token using the kind and span.
#[inline]
pub fn new(kind: TokenKind, span: Span) -> Self {
Token { kind, span }
}

/// Some token that will be thrown away later.
#[inline]
pub fn dummy() -> Self {
Token::new(TokenKind::Dummy, DUMMY_SP)
}

/// Returns an identifier if this token is an identifier.
#[inline]
pub fn ident(&self) -> Option<Ident> {
match self.kind {
Ident(name) => Some(Ident::new(name, self.span)),
_ => std::option::Option::None,
}
}

/// Whether the token is keyword.
#[inline]
pub fn is_keyword(&self, kw: Symbol) -> bool {
self.run_on_ident(|id| id.name == kw)
}

/// Whether the token is a string literal token.
#[inline]
pub fn is_string_lit(&self) -> bool {
match self.kind {
TokenKind::Literal(lit) => {
Expand Down
Loading

0 comments on commit 9e7c0d2

Please sign in to comment.