Raku Books / Creating a Compiler in Raku / Building AST

Array and hash assignment and initialisation

We skipped initialising arrays and hashes and did it for a reason. Let us first make the AST for cases when you declare and assign in two different statements, as shown in the next example:

my d[];
d = 3, 5, "7";

my e{};
e = "1": 2, "3": "4";

Array and hash assignment is different from assigning individual elements, so these actions require their own types of AST nodes:

class AST::ArrayAssignment is ASTNode {
    has Str $.variable-name;
    has ASTNode @.elements;
}

class AST::HashAssignment is ASTNode {
    has Str $.variable-name;
    has Str @.keys;
    has ASTNode @.values;
}

For arrays, we collect the elements in the array consisting of some AST nodes. For hashes, the keys and the values are kept in separate arrays, and keys are forced to be strings. Alternatively, we could create Pair (that’s on of the Raku’s built-in types) objects to combine the key—value pairs.

In the actions, we have to carefully separate assignments to scalars, arrays and hashes. Multi-methods work quite well here, while it may require a lot of conditions in the where clause. Another approach would be to split the assignment rule into three, one per the data type. Anyway, here’s our current implementation of the action methods:

multi method assignment($/ 
    where !$<index> && !$<value>) {
    $/.make(AST::ScalarAssignment.new(
        variable-name => ~$<variable-name>,
        rhs => $<value>[0].made
    ));
}

multi method assignment($/ 
    where !$<index> && $<value> && !$<string>) {
    $/.make(AST::ArrayAssignment.new(
        variable-name => ~$<variable-name>,
        elements => $<value>.map: *.made
    ));
}

multi method assignment($/ 
    where !$<index> && $<value> && $<string>) {
    $/.make(AST::HashAssignment.new(
        variable-name => ~$<variable-name>,
        keys => ($<string>.map: *.made.value),
        values => ($<value>.map: *.made)
    ));
}

Examine this code carefully: you should understand how method signatures help to select the right method for each given situation. In this code, a lot of map methods are used too, they are always concise and mostly clear but not always easy to write.

The next step is to complete the assignment with initialisation actions, and we already have the code for them. Extract it to separate functions (as we did earlier in the interpreter), and here is the final result.

Array-related methods:

sub init-array($variable-name, $elements) {
    return AST::ArrayAssignment.new(
        variable-name => $variable-name,
        elements => $elements.map: *.made
    );
}

multi method array-declaration($/ where $<value>) {
    $/.make(init-array(~$<variable-name>, $<value>));
}

multi method assignment($/ where !$<index> && $<value> && !$<string>) {
    $/.make(init-array(~$<variable-name>, $<value>));
}

The code looks transparent and self-explanatory again. Repeat the same for the hash-related code:

sub init-hash($variable-name, $keys, $values) {
    return AST::HashAssignment.new(
        variable-name => $variable-name,
        keys => ($keys.map: *.made.value),
        values => ($values.map: *.made)
    );
}

multi method hash-declaration($/
    where $<string> && $<value>) {
    $/.make(init-hash(~$<variable-name>, 
                  $<string>, $<value>));
}

multi method assignment($/
    where !$<index> && $<value> && $<string>) {
    $/.make(init-hash(~$<variable-name>, 
                      $<string>, $<value>));
}

Many of the grammar rules are now covered. Let us continue with filling the gaps in the new architecture of the compiler and implement the AST elements for expressions and function calls. Our goal is to either have action methods that just pass the made value to the next level or which put one of the AST::* objects in the made attribute.

Course navigation

Working with indices   |   AST for expressions