Raku Books / Creating a Compiler in Raku / Parsing a Number
Getting the value
The number has been parsed but what is its value? For a compiler, we
need not only to check the validity of the number format but also to
convert it from a string to a number, integer or floating-point. In this
section, we will append actions to the Number grammar so
that we can build the number and finally print it.
Let us start with integers and keep the number in a global variable.
my $n = 0;
class NumberActions {
method integer($/) {
$n = +$/;
}
}Everything looks simple here. An integer value is derived directly
from converting the match piece of the string to a numeric value using
the + prefix operator. To see how it works, let us change
the main test loop so that it prints the parsed value:
for @cases -> $number {
my $test = Number.parse($number, :actions(NumberActions));
if ($test) {
say "OK $number = $n";
}
else {
say "NOT OK $number";
}
}With non-negative integer numbers, it works well:
OK 7 = 7
OK 77 = 77
OK -84 = 84
OK +7 = 7
OK 0 = 0The negative number did not work. It looks like the +7
string was processed correctly, but actually that’s not true as we
completely ignored the sign. This time, the task is a bit more
complicated. The first idea is to flip the sign if we meet the minus
sign:
method sign($/) {
$n *= -1 if ~$/ eq '-';
}That does not work though, as the sign is parsed before the digits
are parsed, and negating $n means applying a sign to zero.
We can use a separate variable to keep the information about the sign
but that’s not the best thing probably. But let’s do that because this
will reveal another problem.
my $n = 0;
my $sign = 1;
class NumberActions {
method integer($/) {
$n = $sign * +$/;
}
method sign($/) {
$sign = -1 if ~$/ eq '-';
}
}This helps to detect the first negative number, but it breaks all the
rest. Of course, you can check if the sign was '+' but the
problem is that the $n an $sign variables are
global, and they must be reset before the next variable is parsed. This
is a good time to move them into the actions class.
class NumberActions {
has $.n = 0;
has $!sign = 1;
method integer($/) {
$!n = $!sign * +$/;
}
method sign($/) {
$!sign = -1 if ~$/ eq '-';
}
}The $n variable is intentionally made a public data
member as we have to get the result somehow. You also need to change the
test loop.
for @cases -> $number {
my $actions = NumberActions.new();
my $test = Number.parse($number, :actions($actions));
if ($test) {
say "OK $number = " ~ $actions.n;
}
else {
say "NOT OK $number";
}
}The main change here is that you pass an instance of the
NumberActions class to the parse method of the grammar.
Now, in each iteration, the parser creates its own variables to keep the
result.
We moved far enough to have all the integers be parsed correctly:
OK 7 = 7
OK 77 = 77
OK -84 = -84
OK +7 = 7
OK 0 = 0For the floating-point numbers, it does not work as smoothly:
OK 3.14 = 14
OK -2.78 = -78
OK 5.0 = 0
OK .5 = 5
OK -5.3 = -3
OK -.3 = -3
OK 3E4 = 4
OK -33E55 = -55
OK 3E-3 = -3
OK 3.14E2 = 2
OK .5E-3 = -3As you can see, either the decimal or the exponential part wins. In
both cases, that was the last integer part that went through the
grammar. Indeed, we transformed the grammar earlier to factor out the
repeating parts. The bell rang when we had to introduce the
$sign variable but now we are suffering even more. All that
needs to be handled differently. And here is how AST can help.
Course navigation
← Finding a number | Using AST →