Here is a sneak peek on a new late-night project I’m working on. An AL compiler written entirely in cloud restricted AL code.

In this video, Erik reveals a secret project he’s been working on during late evening sessions over the course of two weeks: building an AL compiler written entirely in AL, running inside Business Central. He walks through the architecture of the compiler — from the lexer to the parser to the interpreter — and demonstrates it evaluating expressions, executing statements, handling procedures with parameters and return values, and even performing database operations.
Why Build an AL Compiler in AL?
Erik starts with the most important question: why? There are several motivations behind this project:
- User-injectable code: In many projects, Erik has wanted to give users the ability to inject code into different operations. His company eFocus has a product called WSSN — a website tool that creates websites with data and business logic from Business Central. It already has a small, primitive PHP-style compiler built in, and taking it to the next level would require something more dynamic.
- BCCL use cases: There are cases in BCCL (his build tool) where injecting code at runtime would be very useful.
- The scratchpad experience: Erik misses the old C/SIDE experience where you could type a few lines in a codeunit, press Ctrl+R, and it would just run — no deployments, no waiting. It kept developers in a very tight feedback loop.
- Long-standing interest in compilers: Going back to university and compiler construction courses, Erik has always been interested in this space. When Microsoft released the source code for the Roslyn compiler (the C# compiler), and given that the current AL compiler is Roslyn-inspired, it reignited that interest.
Erik was particularly inspired by Immo Landwerth, one of the .NET team members at Microsoft, who built a compiler inspired by Roslyn. That got Erik thinking: maybe this is actually possible to do in AL.
The Object Model Problem (Solved with JSON)
The first barrier to building a compiler in AL is the lack of a proper object model. AL doesn’t have classes or inheritance. But as Erik argued in a previous video about JSON data types, the JsonObject, JsonArray, JsonValue, and JsonToken types can actually be used to create an object model. It’s a bit unconventional, but it works.
Naming the Project
Erik is clear that this will never be complete AL — he has no intention of implementing the full language. So it needs its own name. He’s gone through several candidates: “BC Script,” “Mini AL,” “Embedded AL,” and even joked about “Minnie Mouse.” He asked viewers for their suggestions in the comments.
Demo: Expressions and Statements
The demo takes place inside a Business Central page. Erik explains that there are two fundamental groups of things in the compiler: expressions and statements.
Expressions
An expression evaluates to a value. Starting simple:
- Typing
1and hitting Evaluate returns1. - Typing
1 + 2returns3.
But the result also includes the full JSON representation of the syntax tree. For example, 1 + 2 produces a binary expression syntax node — a concept taken directly from Roslyn. The tree contains a left node (literal expression with value 1), an operator (plus), and a right node (literal expression with value 2).
For a more complex expression like 1 + 2 + 3 - 4, the tree becomes nested: binary expressions within binary expressions, each linked by operators.
Operator Precedence and Parentheses
The compiler correctly handles operator precedence. 1 + 2 * 3 evaluates to 7 (multiplication before addition), while (1 + 2) * 3 evaluates to 9. The parenthesized version shows up in the syntax tree as a parenthesized expression syntax node.
Functions can also be used inside expressions: (1 + 2) * 3 + random(99) works as expected, calling a function within the expression evaluation.
Statements
Statements are standalone commands. Erik demonstrates with a more complete program that includes:
- Global variable declarations
- A procedure
Testwith an if statement that messages different strings based on a condition - A procedure
Test2that declares a vendor record variable, callsFindFirst, and messages the vendor name - A procedure
Test3that takes a parameter, multiplies it by 100, and returns the result - A main
begin..endblock with assignment statements, record operations, and procedure calls
Running this program produces output on a console (Erik can toggle between using Message and writing to a console). Those 30 lines of AL-like code produce roughly 1,000 lines of JSON as the intermediate representation.
Architecture: Three Layers
Erik emphasizes that there are no tricks here — no hidden .NET components, no reused Microsoft DLLs. The project targets cloud and is pure AL. At the time of recording, the entire project is approximately 3,370 lines of AL code with only 99 lines of comments (“this is a midnight project”).
Layer 1: The Lexer
The lexer (sometimes called the “slicer”) is the lowest layer. It takes a text variable containing the source code and keeps track of the current position. Its job is to chop the source code into tokens.
For simple characters, the mapping is direct: an open parenthesis character becomes an OpenParenthesisToken, a close parenthesis becomes a CloseParenthesisToken, a dot becomes its own token, and so on.
For digits, the lexer calls a ReadNumber function that continues reading as long as it encounters digits or dots (to support decimals). The result becomes a number token.
For identifiers, an underscore or letter triggers the ReadIdentifierOrKeyword function. This handles quoted identifiers (with double quotes, as AL supports) and allows digits after the first character. The function then consults a syntax facts codeunit that defines all the reserved keywords — words like if, then, begin, end that have special meaning and can’t be used as variable names. Keywords get their own specific token types.
Layer 2: The Parser
The parser consumes the stream of tokens from the lexer and builds a structured syntax tree. It works linearly, pulling tokens one by one and figuring out what to do with them.
Erik walks through parsing an if statement as an example. The parser:
- Matches the
ifkeyword token - Parses an expression (the condition — e.g.,
random(100) < 50) - Matches the
thenkeyword token - Parses a statement (the "if true" body — which might be another if statement or a block statement with
begin..end) - Optionally parses an
elseclause
The result is passed to an if statement syntax codeunit that creates a new JSON object containing the condition, the statement body, and the else clause. Each object also includes a "kind" field — an integer representing the syntax kind (since enums can't be directly added to JSON).
At the top level, everything feeds into a compilation unit (another Roslyn concept) that contains a list of members and an end-of-file token. Members can be one of three things: a procedure declaration, a variable declaration, or a statement.
Layer 3: The Interpreter
Since Erik isn't outputting machine code or IL, the third layer is an interpreter rather than a code generator. At the time of recording, it's about a third of the total codebase — roughly 1,300 lines in a single codeunit.
The interpreter receives the compilation unit and processes it:
- Global variables are registered in a JSON object. Supported types include text, date, time, integer, decimal, boolean, and record. (Options/enums with the
::syntax were still being worked on.) - Procedures are also added to the globals namespace, since a procedure name occupies a slot — you can't have a variable and a procedure with the same name.
- Statements from the global
begin..endblock are saved and then executed.
Statement Execution
The RunStatement function examines the syntax kind of each object and dispatches accordingly:
- Block statements (
begin..end): Loop through the contained statements and recursively callRunStatementfor each. - Exit statements: Set a halt flag so execution stops as quickly as possible.
- Assignment statements (
variable := expression): Look up the identifier in globals, determine the type, evaluate the expression, and store the result back. For dotted assignments likerecord.field, the field type is determined from the field reference. - Function calls: The most complex case, handling both dotted calls (like
Customer.FindFirstorVendor.Modify) and non-dotted calls (likeMessageorRandom).
Records and the Array Workaround
Records present a unique challenge: you can't put a record into a JSON structure. The only structure in AL/BC that can hold multiple RecordRef values is an array. So Erik uses a fixed-size array (currently 100 elements) with lookup structures for finding the right record variable by name.
The Call Stack
When calling a procedure, the interpreter manages a call stack implemented as a JSON array. Each procedure call pushes a stack frame containing the procedure's parameters, local variables, and potentially a return value. When the procedure exits, the frame is popped. This allows procedures to call other procedures with proper scoping.
The sequence for a procedure call is:
- Push the procedure's parameters and variables onto the stack
- Run the procedure's body statement
- Pop the stack frame
- If there's a return value, extract it from the popped frame
Built-in Functions and the Format Challenge
Global scope functions like Random are straightforward to implement since the interpreter is written in AL and can just call the real Random function.
However, Format is a beast. AL doesn't have a "joker" or "variant" type that you can pass to Format. This means Erik needs extensive case statements: one for each supported data type as the first parameter, multiplied by the possible types for the third parameter (which can be either an integer or text). With six supported data types, that's 12 separate calls to Format to cover all combinations.
Console Output
The Message function can be redirected to write to a console (a text accumulator) instead of showing actual Business Central message dialogs. This is a simple toggle — if console mode is on, append to a text variable; otherwise, call the real Message function.
Current State and Future
At the time of recording, the compiler/interpreter supports:
- Expressions with operator precedence and parentheses
- Variable declarations (text, date, time, integer, decimal, boolean, record)
- Procedures with parameters and return values
- If statements
- Repeat loops and for loops
- Database operations (FindFirst, Insert, Modify, etc.)
- Built-in functions (Random, Format, Message, etc.)
- Assignment statements
- Exit statements
Erik is candid about the project's uncertain future. It might become part of a commercial product, it might be sold as a standalone tool, it might become open source, or it might be abandoned if a fundamental roadblock is discovered. Some data structures — particularly arrays, lists, and dictionaries — would be very difficult to emulate dynamically.
He also mentions wanting to support pre-compiled code that can be saved, loaded, and run later — essentially serializing the parsed JSON syntax tree and deserializing it for execution without re-parsing.
Summary
Erik's late-night project demonstrates that it's entirely possible to build a working compiler and interpreter in pure AL, running inside Business Central with no external dependencies. By leveraging JSON data types as a makeshift object model, and following Roslyn's architecture of lexer → parser → syntax tree, the project can already handle a meaningful subset of AL-like code. Whether it becomes a product, a tool, or an open-source curiosity, it's a fascinating exploration of what's possible within the constraints of the AL language and the Business Central platform.