Raku Books / Creating a Compiler in Raku / Functions / User-defined functions
Calling a function
A call consists of the function name and a list of arguments in parentheses:
rule user-function-call {
<function-name=variable-name> '(' <value>* %% ',' ')'
}It may appear in two contexts: as a standalone statement and inside an expression. Both the statement rule and the deepest expression level receive the new alternative:
rule statement {
| <statement=variable-declaration>
| <statement=return-statement>
| <statement=user-function-call>
| <statement=assignment>
| <statement=function-call>
}
multi rule expr(6) {
| <number>
| <function-call>
| <user-function-call>
| <variable-name> <index>?
| '(' <expression> ')'
}The user-function-call alternative must be listed before
the plain variable-name: when the parser sees
add(3, 4), we want it to try a function call first. If
there are no parentheses after the name, the alternative fails, and the
parser falls back to a variable.
The AST node for a call keeps the name, the argument nodes, and a reference to the evaluator, and it computes its value by asking the evaluator to perform the call:
class AST::UserFunctionCall is ASTNode {
has Str $.function-name;
has ASTNode @.arguments;
has $.evaluator;
method value() {
return $.evaluator.call-user-function(
$!function-name, @!arguments);
}
}The action method offers no surprises:
method user-function-call($/) {
$/.make(AST::UserFunctionCall.new(
function-name => ~$<function-name>,
arguments => ($<value> ?? ($<value>.map: *.made) !! ()),
evaluator => $!evaluator,
));
}The most interesting part is, of course, the call itself. Here is its first version:
method call-user-function($function-name, @arguments) {
die "Unknown function $function-name"
unless %!func{$function-name}:exists;
my $function = %!func{$function-name};
my @values = @arguments.map: *.value;
my %saved-vars = %!var;
%!var = ();
for ^$function.parameters.elems -> $i {
%!var{$function.parameters[$i]} = @values[$i];
}
my $result = 0;
self.eval-node($_) for $function.statements;
%!var = %saved-vars;
return $result;
}Let us walk through it. First, the function must have been defined
earlier; calling an unknown name is a runtime error. Second, the
argument values are computed before anything else changes. The arguments
may refer to the variables of the caller, so we have to evaluate them
while those variables are still in place. Notice that the result is
assigned to the @values array, which makes the computation
eager. A lazy sequence would be computed too late, when the variables
have already been replaced.
After that, the current variable storage is saved aside, and a fresh,
empty one is created with only the parameters in it. The body is
executed by the same eval-node machinery that runs the main
program. Finally, the saved variables are restored. The method returns 0
for now; the return statement is our next topic. A
statement-level call also needs a multi-method in the evaluator, which
computes the value and throws it away:
multi method eval-node(AST::UserFunctionCall $node) {
$node.value;
}The first user-defined function in Lingua is ready to run:
func greet(name) {
say "Hello, $name!";
}
greet("World"); ## Hello, World!
greet("Lingua"); ## Hello, Lingua!