The Curious Case of the Case Statement in AL

While working on completing my BCScript (AL) compiler/Interpreter, I realized that the case statement in AL is not like the case statement in Pascal. Follow along the discovery in this video:

https://youtu.be/Vj1ObSphZYU

In this video, Erik dives into an unexpected discovery about how the case statement works in AL for Business Central. If you’re coming from a Pascal or most other language backgrounds, you might assume that case labels must be constants — but AL has a surprise in store. Erik walks through the behavior, demonstrates it with live code and the debugger, and explores what this means for compiler optimization.

The Basics: Case Statements in AL

If you’re coming from another language, you’d think of the case statement as a switch statement. In AL, it works like this:

case age of
    0:
        Message('Welcome to the world');
    1, 2, 3, 4, 5, 6:
        Message('Not in school');
    7 .. 18:
        Message('In school');
    19 .. 99:
        Message('Working');
end;

With age set to 16, this evaluates to “In school” — no surprises there.

Overlapping Ranges and Order of Evaluation

In most languages, if you add a duplicate case label that’s already covered by another case (or a range), you’d get a compiler error. In AL, that’s not entirely the case. If you add a specific case for 16 alongside the range 7 .. 18, AL compiles without complaint.

The order matters though: whichever case appears first wins. If the explicit 16: case is placed before the 7 .. 18 range, it matches first. If the range comes first, the specific 16 case is never reached. If you add two identical constant labels (e.g., two entries for 16), AL will flag that. But when a range overlaps with a specific value, it’s silent.

The Real Discovery: Expressions as Case Labels

This is where things get interesting. In classic Pascal — going all the way back to the early 1980s — the values before the colon in a case statement must be constants. But the AL documentation says that a value set can be an expression or a range.

That means you can write something like this:

case age of
    8 + 8:
        Message('Eight plus eight!');
    16:
        Message('Sweet sixteen');
end;

And it works. 8 + 8 evaluates to 16, and the first match wins. But it goes further than just arithmetic — you can use function calls as case labels.

Function Calls in Case Labels

Here’s the source code Erik demonstrates. A simple test function that returns whatever integer you pass in:

procedure test(i: Integer): Integer
begin
    //message('Testing %1', i);
    exit(i);
end;

And the case statement using function calls as value sets:

case age of
    test(0):
        Message('Welcome to the world');
    test(1):
        Message('Learn to walk');
    test(16):
        Message('Sweet sixteen');
    else
        message('Anything else!');
end;

With age set to 16, this correctly outputs “Sweet sixteen.” But the implications are profound: every case label is evaluated at runtime. When you uncomment the message inside the test function, you can see that test(0), test(1), and test(16) are all called sequentially until a match is found.

It’s Really Just an If-Else Chain

This means the case statement in AL is semantically equivalent to a chain of if...else if statements:

if test(0) = 0 then
    message('Welcome to the world')
else if test(1) = 1 then
    message('Learn to walk')
else if test(16) = 16 then
    message('Sweet sixteen!')
else
    message('Anything else!');

Erik notes an irony here: the AL code analyzer will actually suggest converting if...else if chains into case statements for readability. Traditionally, you’d assume this also enables optimization — a compiler could use a hash table or jump table for constant case labels. But since AL allows arbitrary expressions (including function calls), those optimizations are impossible in the general case.

A Debugger Mystery

During the demo, Erik stumbled onto an odd behavior: when running with the debugger attached and a breakpoint set, the OnOpenPage trigger appeared to execute multiple times (two or three times instead of once). Without the debugger, the code ran exactly once. Erik wasn’t able to explain this during the video and invited viewers to share their theories — it may be related to how the Business Central web client initializes pages or how the debugger attaches to the session.

Impact on Compiler Design

Erik discovered all of this while implementing case statement support in his own AL compiler (for his BC Script tool). He was initially designing an optimized implementation using hash tables for value lookups and jump tables for fast dispatch — the kind of optimizations that are standard for constant case labels in languages like C or Pascal.

Once he realized that AL case labels can be arbitrary expressions evaluated at runtime, all of those optimization strategies went out the window. The case statement essentially must be compiled as a sequential evaluation, checking each label expression in order.

Full Source Code

Here’s the complete page extension Erik used for the demonstration:

pageextension 50117 CustomerListExt extends "Customer List"
{
    trigger OnOpenPage();
    var
        age: Integer;
    begin
        age := 16;

        if test(0) = 0 then
            message('Welcome to the world')
            else if test(1) = 1 then
                message('Learn to walk')
                else if test(16) = 16 then
                    message('Sweet sixteen!')
                else
                    message('Anything else!');
                    
        case age of
            test(0):
                Message('Welcome to the world');
            test(1):
                Message('Learn to walk');
            test(16):
                Message('Sweet sixteen');
            else
                message('Anything else!');
        end;
    end;

    procedure test(i: Integer): Integer
    begin
        //message('Testing %1', i);
        exit(i);
    end;
}

Conclusion

The AL case statement is more powerful — and more surprising — than you might expect if you’re coming from a Pascal background. Unlike traditional Pascal where case labels must be compile-time constants, AL allows full expressions, including function calls, as case labels. These are evaluated sequentially at runtime until a match is found, making the case statement functionally equivalent to an if...else if chain. While this adds flexibility, it also means the compiler can’t apply the jump table or hash-based optimizations that make case statements fast in other languages. It’s a subtle but important difference to be aware of when writing AL code for Business Central.