Execution
Running code in Erik's ToolBox follows a well-defined execution flow that takes your AL source code through compilation, validation, and interpretation. This page explains the complete execution process, how errors are reported, how to structure your programs with variables and procedures, and the data types available for your scripts.
The Run Action
To execute your code, press F9 on your keyboard or click the Run action in the toolbar. This is the primary way to trigger execution. When you press Run, the ToolBox requests the current source code from the editor control and passes it to the execution pipeline.
If your code does not contain a begin keyword, the ToolBox automatically wraps it in a begin...end; block. This means you can type a single expression or statement and run it directly without writing the enclosing block structure. For example, if you type:
message('Hello, World!');
The ToolBox wraps this as:
begin
message('Hello, World!');
end;
This convenience feature makes it faster to run simple one-line operations.
Execution Flow
When the Run action is triggered, the following sequence occurs inside the Execute() procedure:
Step 1: Parse the Source Code
The source code is passed to Compiler.Parse(). This codeunit orchestrates the full compilation pipeline:
- The Parser (codeunit Parser) is initialized with the source code.
- The Parser first invokes the Lexer (codeunit Lexer), which tokenizes the source code into a stream of syntax tokens.
- The Parser reads the token stream and builds an Abstract Syntax Tree (AST) -- a JSON object that represents the hierarchical structure of your program.
- The Parser's
ParseComilationUnit()method returns the root AST node. - The Compiler collects any diagnostics (errors) generated by the Parser.
Step 2: Validate the AST
If the Parser produced no errors, the Compiler performs an additional validation step by calling Interpreter.TestUnit() on the parsed AST. This step catches semantic issues such as:
- Duplicate variable declarations
- Unknown variable references
- Incomplete variable definitions
- Unknown data types
- Unknown option members
Any diagnostics from this validation phase are merged with the Parser's diagnostics.
Step 3: Check for Compilation Errors
The Compiler checks HasErrors() to determine whether any diagnostics were generated. If errors exist:
- The error messages are displayed in the output pane with line numbers and descriptions.
- They are also recorded as the outcome of the last run, so the ToolBox Agent can read them when you ask it why the run failed.
If there are no compilation errors, execution proceeds to the next step.
Step 4: Initialize the Interpreter
Before executing the AST, the system performs several setup tasks:
- Console capture is enabled (if the "Show output in console" toggle is on), which redirects
message()calls to the console pane. - The Excel management codeunit is informed of the current program name and source code, enabling Excel integration during execution.
- The user's access level is loaded from the ToolBox User table and passed to the compiler registration module, which enforces permissions during execution.
- The license status is verified. In production SaaS environments, a valid license is required.
- The remote environment settings are configured (Application ID, Secret, and Tenant ID) for scripts that access data in other environments.
- A
Commit()is issued to save any pending database transactions before execution begins.
Step 5: Execute the AST
The Interpreter's RunUnit() method is called with the parsed AST. This method walks the tree structure and executes each node:
- Variable declarations allocate storage for your variables.
- Assignment statements evaluate the right-hand expression and store the result.
- Control flow statements (if/then/else, for, while, repeat/until, case) direct the execution path.
- Procedure calls execute named procedures defined in your script.
- Record operations interact with Business Central tables through the interpreter's built-in record handling.
- Built-in function calls execute functions like
message(),format(),strlen(), and others. - Expression evaluations compute arithmetic, string, and boolean expressions.
Step 6: Collect and Display Results
After execution completes:
- If console capture is active, the collected console output (all
message()calls plus timing information) is loaded into the output pane. - If the Interpreter detected runtime errors, the error diagnostics are displayed in the output pane and recorded as the outcome of the last run for the ToolBox Agent.
- If no errors occurred, the output pane shows the console output with start time, end time, and elapsed duration.
Handling Compilation Errors
Compilation errors prevent your code from executing. When errors are detected, the output pane displays them in the following format:
[DiagnosticType] [Details] at line [LineNumber]
Each diagnostic includes a type that tells you the category of error:
| Diagnostic Type | What It Means | Example Message |
|---|---|---|
| Unexpected | The parser found a token that does not belong in the current context. | Unexpected Got Semicolon, was expecting Identifier at line 5 |
| NotImplemented | The code uses a language construct that the ToolBox does not yet support. | NotImplemented Record is not implemented in BCScript (yet) at line 3 |
| DuplicateIdentifier | A variable name has been declared more than once. | DuplicateIdentifier Declared twice: i at line 8 |
| IncompleteVariable | A variable declaration is missing required information. | IncompleteVariable Variable declared twice: MyVar at line 4 |
| UnknownType | The type specified in a variable declaration is not recognized. | UnknownType SomeType at line 2 |
| UnknownVariable | Code references a variable that was never declared. | UnknownVariable MyVar at line 10 |
| UnknownOptionMember | An option value does not exist for the referenced option field. | UnknownOptionMember InvalidOption at line 7 |
| Error | A general error with a descriptive text message. | Error [description] at line 6 |
Use the line number to locate the problem in your source code. Fix the issue and press F9 again to retry.
Runtime Errors
Runtime errors occur during execution after successful compilation. These include:
- Database errors -- Record not found, permission denied, or constraint violations when performing record operations.
- Type errors -- Attempting an operation on incompatible types, such as adding a text to a number.
- Division by zero -- Dividing a number by zero.
- Business logic errors -- Errors raised by Business Central validation triggers or business rules when you call
Validate()orInsert(true). - Custom errors -- Errors raised by your own code using the
error()function.
When a runtime error occurs, execution stops immediately. The error message is displayed in the output pane. If console capture was active, any output generated before the error is also displayed so you can see how far execution progressed.
The Interpreter tracks runtime diagnostics separately from compilation diagnostics using a Runtime Error type, which includes the error message and location information.
Writing Programs with Procedures
Your ToolBox scripts can define procedures (functions) in addition to the main begin...end block. Procedures let you organize your code into reusable units within a single script.
A program with procedures follows this structure:
var
// Global variables
TotalCount : Integer;
procedure CountRecords(TableName : Text) : Integer
var
Cust : Record Customer;
begin
if Cust.FindSet() then
repeat
TotalCount += 1;
until Cust.Next() = 0;
exit(TotalCount);
end;
begin
// Main entry point
CountRecords('Customer');
message('Total customers: %1', TotalCount);
end;
Procedure Structure
- Procedures are declared with the
procedurekeyword before the mainbegin...endblock. - Each procedure can have its own
varsection for local variables. - Procedures can accept parameters and return values.
- Procedures can call other procedures defined in the same script.
- The main
begin...endblock is the entry point that executes first.
Using Variables
Variables in ToolBox scripts are declared in var sections. You can declare variables in two scopes:
Global Variables
Global variables are declared in the var section before the first begin keyword or before any procedure declarations. They are accessible throughout the entire script, including inside all procedures.
var
MyText : Text;
MyNumber : Integer;
Cust : Record Customer;
begin
// MyText, MyNumber, and Cust are accessible here
end;
Local Variables
Local variables are declared in the var section inside a procedure. They are only accessible within that procedure.
procedure MyProcedure()
var
LocalVar : Decimal;
begin
LocalVar := 3.14;
message('Value: %1', LocalVar);
end;
Variable Declaration Syntax
The basic syntax for declaring a variable is:
VariableName : DataType;
For record variables, you specify the table name:
Cust : Record Customer;
SalesHeader : Record "Sales Header";
Table names with spaces or special characters must be enclosed in double quotes.
Accessing Business Central Data
One of the most powerful aspects of the ToolBox is direct access to Business Central tables. You declare a record variable, use record methods to query and manipulate data, and access field values through dot notation.
Basic Record Operations
var
Item : Record Item;
begin
// Find all items
if Item.FindSet() then
repeat
message('Item: %1 - %2', Item."No.", Item.Description);
until Item.Next() = 0;
end;
Filtering Records
var
Cust : Record Customer;
begin
Cust.SetRange("Country/Region Code", 'US');
Cust.SetFilter(Name, '@*cont*');
if Cust.FindSet() then
repeat
message('%1: %2', Cust."No.", Cust.Name);
until Cust.Next() = 0;
end;
Modifying Records
var
Cust : Record Customer;
begin
if Cust.Get('10000') then begin
Cust."Phone No." := '555-0100';
Cust.Modify();
message('Updated phone for %1', Cust.Name);
end;
end;
Inserting Records
var
Contact : Record Contact;
begin
Contact.Init();
Contact."No." := 'CT99999';
Contact.Name := 'Test Contact';
Contact.Insert(true);
message('Inserted contact %1', Contact."No.");
end;
Supported Data Types
The ToolBox interpreter supports the following data types for variables:
| Data Type | Description | Example Declaration |
|---|---|---|
| Integer | Whole numbers (32-bit) | i : Integer; |
| BigInteger | Whole numbers (64-bit) | Big : BigInteger; |
| Decimal | Numbers with decimal places | Amount : Decimal; |
| Text | Variable-length text strings, optionally sized | Name : Text; / Name : Text[100]; |
| Code | Uppercase text, typically for identifiers | ItemNo : Code[20]; |
| Boolean | True or false values | Found : Boolean; |
| Date | Calendar dates | StartDate : Date; |
| Time | Time of day values | StartTime : Time; |
| DateTime | Combined date and time | Created : DateTime; |
| Duration | Time span values in milliseconds | Elapsed : Duration; |
| Guid | Globally unique identifiers | Id : Guid; |
| Option | Enumerated values with their members | Status : Option Open,Released,Closed; |
| DateFormula | Relative date calculations | Formula : DateFormula; |
| Char / Byte | A single character or byte | C : Char; |
| Record | Reference to a Business Central table, optionally temporary |
Cust : Record Customer; |
| Dialog | Progress dialog window | Dlg : Dialog; |
| array[N] of T | Fixed-size, 1-based array | Names : array[8] of Text; |
| List of [T] / Dictionary of [K, V] | Dynamic collections | Names : List of [Text]; |
| JsonObject / JsonArray / JsonToken / JsonValue | JSON values | Obj : JsonObject; |
| InStream / OutStream | Streams | InS : InStream; |
| Codeunit | One of the supported standard codeunits | M : Codeunit Math; |
Types such as TextBuilder, Blob, Enum, RecordRef, and HttpClient are not available; see Limitations and Differences from Standard AL.
Performance Considerations
When working with large data sets, keep the following in mind:
- Limit result sets. Use
SetRange()andSetFilter()to narrow down records before iterating. Processing all records in a large table can be slow and may time out. - Use
FindSet()for iteration. When you plan to loop through records,FindSet()is more efficient thanFindFirst()followed byNext(). - Minimize
CalcFields()calls. FlowFields requireCalcFields()to retrieve their values, but each call involves a database query. Only callCalcFields()for fields you actually need. - Batch modifications. When updating many records, consider using
ModifyAll()for single-field updates rather than looping with individualModify()calls. - Console output volume. Writing thousands of lines to the console can slow down the display. For large data operations, consider writing results to Excel using
exset()instead ofmessage(). - Commit strategically. Use
committo save data in stages during long-running operations. This prevents losing all changes if a later operation fails.