Rand Stats

ASTQuery

zef:FCO

Actions Status

NAME

ASTQuery - Query and manipulate Raku’s Abstract Syntax Trees (RakuAST) with an expressive syntax

SYNOPSIS

use ASTQuery;

# Sample Raku code
my $code = q{
    for ^10 {
        if $_ %% 2 {
            say $_ * 3;
        }
    }
};

# Generate the AST
my $ast = $code.AST;

# Example 1: Find 'apply-op' nodes where left operand is 1 and right operand is 3
my $result1 = $ast.&ast-query('.apply-op[left=1, right=3]');
say $result1.list;  # Outputs matching nodes

Output

[
  RakuAST::ApplyInfix.new(
    left  => RakuAST::IntLiteral.new(1),
    infix => RakuAST::Infix.new("*"),
    right => RakuAST::IntLiteral.new(3)
  )
]

DESCRIPTION

ASTQuery simplifies querying and manipulating Raku’s ASTs using a powerful query language. It allows precise selection of nodes and relationships, enabling effective code analysis and transformation.

Key Features

QUERY LANGUAGE SYNTAX

Node Description

Format:

RakuAST::Class::Name.group#id[attr1, attr2=attrvalue]$name

Components:

Note: Use only one $name per node.

Operators

Note: The space operator is no longer used.

Ignorable Nodes

Nodes skipped by >> and << operators:

EXAMPLES

Example 1: Matching Specific Infix Operations

# Sample Raku code
my $code = q{
    for ^10 {
        if $_ %% 2 {
            say 1 * 3;
        }
    }
};

# Generate the AST
my $ast = $code.AST;

# Query to find 'apply-op' nodes where left=1 and right=3
my $result = $ast.&ast-query('.apply-op[left=1, right=3]');
say $result.list;  # Outputs matching 'ApplyOp' nodes

Output

[
  RakuAST::ApplyInfix.new(
    left  => RakuAST::IntLiteral.new(1),
    infix => RakuAST::Infix.new("*"),
    right => RakuAST::IntLiteral.new(3)
  )
]

Explanation:

Example 2: Using the Ancestor Operator <<< and Named Captures

# Sample Raku code
my $code = q{
    for ^10 {
        if $_ %% 2 {
            say $_ * 3;
        }
    }
};

# Generate the AST
my $ast = $code.AST;

# Query to find 'Infix' nodes with any ancestor 'conditional', and capture 'IntLiteral' nodes with value 2
my $result = $ast.&ast-query('RakuAST::Infix <<< .conditional$cond .int#2$int');
say $result.list;  # Outputs matching 'Infix' nodes
say $result.hash;  # Outputs captured nodes under 'cond' and 'int'

Output

[
  RakuAST::Infix.new("%%"),
  RakuAST::Infix.new("*")
]
{
  cond => [
    RakuAST::Statement::If.new(
      condition => RakuAST::ApplyInfix.new(
        left  => RakuAST::Var::Lexical.new("$_"),
        infix => RakuAST::Infix.new("%%"),
        right => RakuAST::IntLiteral.new(2)
      ),
      then => RakuAST::Block.new(...)
    )
  ],
  int => [
    RakuAST::IntLiteral.new(2),
    RakuAST::IntLiteral.new(2)
  ]
}

Explanation:

Example 3: Using the Ancestor Operator << with Ignorable Nodes

# Find 'Infix' nodes with an ancestor 'conditional', skipping only ignorable nodes
my $result = $ast.&ast-query('RakuAST::Infix << .conditional$cond');
say $result.list;  # Outputs matching 'Infix' nodes
say $result.hash;  # Outputs captured 'conditional' nodes

Explanation:

Example 4: Using the Parent Operator < and Capturing Nodes

# Sample Raku code
my $code = q{
    for ^10 {
        if $_ %% 2 {
            say $_ * 2;
        }
    }
};

# Generate the AST
my $ast = $code.AST;

# Query to find 'ApplyInfix' nodes where right operand is 2 and capture them as '$op'
my $result = $ast.&ast-query('RakuAST::Infix < .apply-op[right=2]$op');
say $result<op>;  # Captured 'ApplyInfix' nodes

Output

[
  RakuAST::ApplyInfix.new(
    left  => RakuAST::Var::Lexical.new("$_"),
    infix => RakuAST::Infix.new("*"),
    right => RakuAST::IntLiteral.new(2)
  )
]

Explanation:

Example 5: Using the Descendant Operator >>> and Capturing Variables

# Sample Raku code
my $code = q{
    for ^10 {
        if $_ %% 2 {
            say $_;
        }
    }
};

# Generate the AST
my $ast = $code.AST;

# Query to find 'call' nodes that have a descendant 'Var' node and capture the 'Var' node as '$var'
my $result = $ast.&ast-query('.call >>> RakuAST::Var$var');
say $result.list;  # Outputs matching 'call' nodes
say $result.hash;  # Outputs the 'Var' node captured as 'var'

Output

[
  RakuAST::Call::Name::WithoutParentheses.new(
    name => RakuAST::Name.from-identifier("say"),
    args => RakuAST::ArgList.new(
      RakuAST::Var::Lexical.new("$_")
    )
  )
]
{ var => RakuAST::Var::Lexical.new("$_") }

Explanation:

RETRIEVING MATCHED NODES

The ast-query function returns an ASTQuery object with:

Accessing captured nodes:

# Perform the query
my $result = $ast.&ast-query('.call#say$call');

# Access the captured node
my $call_node = $result<call>;

# Access all matched nodes
my @matched_nodes = $result.list;

THE ast-query FUNCTION

Usage:

:lang<raku> my $result = $ast.&ast-query('query string');

Returns an ASTQuery object with matched nodes and captures.

GET INVOLVED

Visit the ASTQuery repository on GitHub for examples, updates, and contributions.

How You Can Help

Note: ASTQuery is developed by Fernando Corrêa de Oliveira.

DEBUG

For debugging, use the ASTQUERY_DEBUG env var.

Trace example

CONCLUSION

ASTQuery empowers developers to effectively query and manipulate Raku’s ASTs, enhancing code analysis and transformation capabilities.

DESCRIPTION

ASTQuery is a way to match RakuAST

AUTHOR

Fernando Corrêa de Oliveira fernandocorrea@gmail.com

COPYRIGHT AND LICENSE

Copyright 2024 Fernando Corrêa de Oliveira

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.