Raku Books / Creating a Compiler in Raku / Evaluating AST

Interpolating everywhere

Now we have another problem. We only need interpolation for strings, but the eval-node method does a generic call of the value(%!var) method.

It fails on our test program, as two of the three AST::ScalarDeclaration nodes contain references to numbers, not strings, and thus there is no value method that takes a hash. If you change the program so that it only uses strings, it begins to work:

my date = "18";
my month = "October";
my year = "2018";
. . .

We have to find the way to make the code workable with both strings and numbers but keep it simple. One of the possible solutions is to check the type of the AST node each time it is used in situations when the value is taken. Another solution is to define a method that takes the parameter in the AST::NumberValue class. Both approaches seem to be quite line-consuming and redundant. Let us instead allow the AST nodes to access the variables directly.

We pass the evaluator object to the instance of LinguaActions and keep a reference to it in the node variables. The main program must be modified a bit, a few objects must be created before the actual parsing.

my $evaluator = LinguaEvaluator.new();
my $actions = LinguaActions.new(:evaluator($evaluator));
my $ast = Lingua.parse($code, :actions($actions));

if $ast {
    say 'OK';
    $evaluator.eval($ast.made);
}
else {
    error('Error: parse failed');
}

An evaluator object of the LinguaEvaluator type is passed to the constructor of the actions class. Notice that we again pass an instance of LinguaActions to the parse method, not just the class name.

As we are willing to use the variables outside of the evaluator class, let us declare it public by changing its twigil to a dot.

class LinguaEvaluator {
    has %.var;
    . . .
}

(As we saw from the previous section, data methods that would be called public in other programming languages, are a combination of an attribute and a getter method in Raku.)

In the LinguaActions class, add a place for keeping the evaluator:

use LinguaEvaluator;
 
class LinguaActions {
    has LinguaEvaluator $.evaluator;
    . . .
}

We are passing it to the AST node, but only when creating an AST::StringValue node:

$/.make(AST::StringValue.new(
    evaluator => $!evaluator,
    value => ~$match,
    interpolations => @interpolations
));

Nodes of other types do not need such reference, as they are not using the data from the evaluator object. So, we only need to change the AST::StringValue class:

class AST::StringValue is ASTNode {
    has Str $.value;
    has @.interpolations;
    has $.evaluator;
    . . .
}

In this class, we will redefine the default value getter with our own method that will interpolate the variables, taking them from the evaluator’s variable storage:

method value() {
    my $s = $!value;

    for @!interpolations.reverse -> $var {
        $s.substr-rw($var[1], $var[2]) = 
            $.evaluator.var{$var[0]};
    }

    . . .
}

Now the code is working again, and the string can successfully interpolate both numbers and strings.

Course navigation

Using Raku getters   |   Arithmetic operations