Rand Stats

ASTQuery

zef:FCO

Actions Status

NAME

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

INSTALLATION

QUICKSTART

use ASTQuery;

my $code = q:to/CODE/;
    sub f($x) { }
    f 42;
    say 1 * 3;
CODE

my $ast = $code.AST;

# Find Apply* operator nodes where left=1 and right=3
my $ops = $ast.&ast-query('.apply-operator[left=1, right=3]');
say $ops.list;

# Find calls that have an Int somewhere under args
my $calls = $ast.&ast-query('&is-call[args=>>>.int]');
say $calls.list;

DESCRIPTION

ASTQuery provides a compact, composable query language for traversing and matching nodes in Raku’s RakuAST. It lets you precisely match nodes, relationships (child/descendant/ancestor), and attributes, capture interesting nodes, and register custom function matchers for reuse.

Key Features

CLI

QUERY LANGUAGE SYNTAX

Node Description

Format:

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

Components:

Operators

Note: The space operator is no longer used.

Attribute Relation Operators

Start traversal from the attribute node (when the attribute value is itself a RakuAST node):

Attribute Value Operators

Inside [attributes] you can apply value operators to an attribute, comparing against a literal string/number, identifier, or a regex literal:

When the attribute value is a RakuAST node, the matcher walks nested nodes via their configured id fields to reach a comparable leaf value (e.g., .call[name] → Name’s identifier). Non-existent attributes never match. Flags in regex literals are not yet supported.

Ignorable Nodes

Nodes skipped by >> and << operators:

Function Matchers (&name)

Register reusable matchers in code and reference them in queries via &name. Functions compose with other constraints using AND semantics.

Built-ins registered on module load:

Groups (Common Aliases)

You can extend these with add-ast-group and add-to-ast-group.

See the full reference in REFERENCE.md for a complete list of groups, built-in functions, and id fields.

AST TRANSFORMATIONS

Use ASTQuery in a CHECK phaser to rewrite the current compilation unit’s AST before runtime.

Example: Add "!!!" at the end of each say call.

use experimental :rakuast;
use ASTQuery;

CHECK {
    my $ast = $*CU;
    for $ast.&ast-query(Q|.call#say).list {
        .args.push: RakuAST::StrLiteral.new: "!!!";
    }
}
say "some text"; # prints "some text!!!"

note

RakuAST remains experimental; and how mutable it's going to be is still being discussed.

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-operator' nodes where left=1 and right=3
my $result = $ast.&ast-query('.apply-operator[left=1, right=3]');
say $result.list;  # Outputs matching 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;  # Infix nodes
say $result.hash;  # 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;  # Infix nodes
say $result.hash;  # 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-operator[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;  # call nodes
say $result.hash;  # '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::Match 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;

PROGRAMMATIC API

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

Set the ASTQUERY_DEBUG env var to see a tree of matcher decisions and deparsed node snippets.

Trace example

CONCLUSION

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

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.