RakuAST for Early Adopters

Elizabeth Mattijsen - May 24 '23 - - Dev Community

Originally, I thought I'd name this series of blog posts "RakuAST for Beginners". But since documentation on RakuAST is pretty non-existent at this stage, I felt I would be doing people a service by making clear that they will be at the very front of development in the Raku Programming Language if they start dabbling in RakuAST.

What Is It?

If you're a programmer, you may be aware of the concept of an Abstract Syntax Tree. That's where the "AST" in RakuAST comes from.

Normally, when you write a program in the Raku Programming Language, all of the business of compiling your code into something that can be executed, is done for you under the hood. One of the steps in this process is to create an Abstract Syntax Tree (aka AST from now on) from your code, and convert that to bytecode that an interpreter (or "virtual machine") such as MoarVM can execute.

So where does RakuAST come into this? Well, RakuAST allows you to create an AST (that can be converted to bytecode and run) programmatically without needing to create intermediate source code.

How ready is it for primetime?

Not (yet). Several areas of RakuAST features and semantics are still un(der)developed. But there is enough implemented to allow the new "Raku" grammar to handle Raku source code well enough to make 64% of Raku test files pass completely. A lot of the work that needs to be done with the "Raku" grammar, is to use the already existing RakuAST features, rather than needing new features in RakuAST itself. But that is worth another series of blog posts in itself.

If you want to run your code with the new Raku grammar, you must set the RAKUDO_RAKUAST=1 environment variable before running. Otherwise the default grammar (which is now referred to as the "legacy" grammar) will be used.

In any case, because it is not ready for primetime (yet), and some interfaces and semantics might still change, one will have to put a use experimental :rakuast in the code. Or indicate you want the current development language version, by putting a use v6.e.PREVIEW in the code.

So when should I use this?

When it is handy for you to do so.

To give you an example: sprintf takes a format string to create a string representation of the values given. This is currently implemented with a special "format-string" grammar. Everytime a sprintf is executed (either directly, or by using printf or .fmt), it will parse the format using that grammar. And the associated actions then produce the string representation for the given values.

Needless to say, this is very repetitive and cpu-intensive. Wouldn't it be better to only parse the format once, and then create code for that, and run that code everytime?

Yes, it would. But until there was RakuAST, that was virtually impossible to do because there was no proper API for building ASTs. Nor was there an interface to execute those ASTs. And now that there is RakuAST, it is actually possible to do this. And there is actually already an implementation of that idea in the new Formatter class. Although this is definitely not intended as an entry point into grokking RakuAST.

But maybe a better way to tell whether it is handy for you to use RakuAST, is to use RakuAST whenever you need to resort to using EVAL. Because with RakuAST, you will have a way to not have to worry about incidental code insertion, and you will be able to create semantics for which there is no way in the Raku Programming Language (yet).

Let's start at the beginning

It is common to start with a "Hello World", so let's start with one here as well. This is the syntax to create an AST for saying "Hello World":

use experimental :rakuast;

my $ast = RakuAST::Call::Name.new(
  name => RakuAST::Name.from-identifier("say"),
  args => RakuAST::ArgList.new(
    RakuAST::StrLiteral.new("Hello world")
  )
);
Enter fullscreen mode Exit fullscreen mode

This creates a RakuAST tree and puts that in the $ast variable. There is no output yet, because all that was done here, was to create the RakuAST objects. To actually convert to bytecode and run that, one needs to call the .EVAL method on the RakuAST object:

$ast.EVAL;  # Hello World
Enter fullscreen mode Exit fullscreen mode

Pretty neat, eh? But that's not all. To help in development and debugging, you can say the .raku method on a RakuAST object, and it will create a representation of the object as RakuAST objects in Raku code.

say $ast.raku;
# RakuAST::Call::Name.new(
#   name => RakuAST::Name.from-identifier("say"),
#   args => RakuAST::ArgList.new(
#     RakuAST::StrLiteral.new("Hello world")
#   )
# );
Enter fullscreen mode Exit fullscreen mode

And since most uses of saying RakuAST objects will be to see this representation, you can actually drop the .raku part there, so say $ast will give you the same output.

Of course, sometimes you would like to see how a RakuAST object would look like as Raku source code. And there's a method for that as well: .DEPARSE:

say $ast.DEPARSE;  # say("Hello World")
Enter fullscreen mode Exit fullscreen mode

Of course, the .DEPARSE output will be a little more formal than original. But it will (usually) be legal source code, and round-trippable. And you could argue that this could be used as a (simple) linter. And you'd be right: the way .DEPARSE is implemented, is that it is pluggable (so one could implement their own way of deparsing RakuAST objects). But that in itself is again enough for a series of blog posts.

Finally, sometimes you would have a piece of source code of which you would like to know the RakuAST representation. And for that, there's the .AST method on strings:

my $ast = 'say "Hello World"'.AST;
say $ast;
# RakuAST::StatementList.new(
#   RakuAST::Statement::Expression.new(
#     expression => RakuAST::Call::Name.new(
#       name => RakuAST::Name.from-identifier("say"),
#       args => RakuAST::ArgList.new(
#         RakuAST::QuotedString.new(
#           segments   => (
#             RakuAST::StrLiteral.new("Hello World"),
#           )
#         )
#       )
#     )
#   )
# )
Enter fullscreen mode Exit fullscreen mode

Note that this is slightly more complex than the initial example. But you hopefully see that that's because this is now wrapped as an expression in a statement, which is part of a statement list. And the double quoted string hasn't been flattened yet.

And it should also be noted that this functionality depends on the "Raku" grammar, which does not yet support all Raku Programming Language functionality. So in some cases, it may still not do what you hoped it would do.

Conclusion

This blog post introduces RakuAST, an interface to create Abstract Syntax Trees in the Raku Programming Language. It shows how to build a "Hello World" AST, and shows how to run the AST, how it was created and how it could be represented as Raku source code.

The intended audience are those people willing to be early adopters of these exciting new features in the Raku Programming Language.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player