Compilation Model
One thing that is unusual for Storm is that it utilizes lazy compilation to only compile the parts of the program that needs to be executed. This means that some errors are not reported immediately, contrary to what happens in most other compiled languages.
The most important implication of this is that it is not possible to conclude that an entire program compiles without errors just because it starts. Rather, it is necessary to either ask the system to compile the entire program, or to ensure that all functions have been called at least once. It is, however, not necessary to provide an extensive test suite to ensure correctness (like in some interpreted languages), since most languages in Storm performs type-checking on the entire function as soon as it is compiled.
The positive side of this is that Storm is able to start running a program faster than if it had to compile all functions up front. Another benefit is that it is possible to test parts of a program that contains errors, as long as the erroneous parts are never executed. This is sometimes beneficial when prototyping changes to a program, or during early development.
The Problem
To illustrate the problem, consider the following program in the file demo.bs
:
void makeBox(Str) { print("-" * x); print(x); print("-" * x); } void greeting(Str name) { print("Hello " + name); } void main() { greeting("Test"); }
If we run this program using storm demo.bs
we immediately get the following error:
@/home/user/storm/demo.bs(16-17): Syntax error: Unexpected ')'. Expected: ":" lang.bs.SDelimiter' lang.bs.SName "[ \n\r\t]*" lang.bs.SType' "<" "->" "[A-Za-z_][A-Za-z0-9_]*" "/" "\[\]" lang.bs.SDelimiter "&" lang.bs.SCommentStart "\?" "//[^\n\r]*[\n\r]"
This is an error that indicates that our code did not parse successfully. Looking up character 16
points us to the line: void makeBox(Str)
. As we can see, we have forgotten to add a variable name
here. If we fix the code by naming the parameter x
, we get the following code:
void makeBox(Str x) { print("-" * x); print(x); print("-" * x); } void greeting(Str name) { print("Hello " + name); } void main() { greeting("Test"); }
If we now run the program (using storm demo.bs
) we get no errors, and the program appears to work
as expected, as it prints Hello Test
. The function makeBox
does, however, contain a type error
that will be reported when the function is executed. We can see this by launching the top-loop and
calling the function manually:
storm -i demo.bs Welcome to the Storm compiler! Root directory: /home/filip/Projects/storm/root Compiler boot in 59.95 ms Type 'licenses' to show licenses for all currently used modules. bs> demo:makeBox("Test") @/home/user/storm/demo.bs(36-37): Syntax error: Can not find an implementation of the operator * for core.Str, core.Str&.
Why was the first error reported immediately, and not the second? To understand this, we need to
understand roughly what Storm needs to do in different stages. When Storm looks for the main
function, it has to first parse all files in the package (only demo.bs
in this case) in order to
see if there is a function named main
or not. The first error we saw happened during this stage:
Storm failed to parse the text in the file, which it had to do to find main
.
The second error, however, was not related to a parsing error. The file parsed successfully, which
allowed Storm to conclude that there was indeed a main
function. As such, Storm did not need to
compile the program further at that time. Since main
only calls greeting
, storm only ever needs
to examine those two functions in detail. Since Storm never had to execute makeBox
, it was never
examined, and therefore the error was never found.
Ensuring All Errors are Reported
There are two ways to force Storm to detect and report the error. The first is simply to make sure
that the function is called at some point. For example, we could add a call to makeBox
inside the
main
function like this:
void main() { greeting("Test"); makeBox("Test"); }
As mentioned before, the test does not have to be exhaustive. It is enough to ensure that all functions are called. Note, however, that the following would not be enough, since the function is not called:
void main() { greeting("Test"); if (false) { makeBox("Test"); } }
Note: The reason is not that Storm is able to determine that the body of the is statement is meaningless. The same would be true even if the condition was more complex.
The second option is to explicitly ask Storm to compile our code ahead of time. This can be done by
calling the compile
member on the relevant named entities. The named entities can be accessed
using the named
syntax like below:
use lang:bs:macro; void makeBox(Str x) { print("-" * x); print(x); print("-" * x); } void greeting(Str name) { print("Hello " + name); } void main() { named{}.compile(); // named{} gets the current package greeting("Test"); }
The compile
function recursively compiles whichever entity it is called on. In this case we ask
Storm to compile everything below the current package, which includes the problematic function. Note
that this means that the error is reported whenever the compile
function is called, but not before
that.
Corrected Code
For completeness, here is the fixed source code:
use lang:bs:macro; void makeBox(Str x) { print("-" * x.count); print(x); print("-" * x.count); } void greeting(Str name) { print("Hello " + name); } void main() { named{}.compile(); greeting("Test"); }