Erik's ToolBox Documentation
Index

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:

  1. The Parser (codeunit Parser) is initialized with the source code.
  2. The Parser first invokes the Lexer (codeunit Lexer), which tokenizes the source code into a stream of syntax tokens.
  3. The Parser reads the token stream and builds an Abstract Syntax Tree (AST) -- a JSON object that represents the hierarchical structure of your program.
  4. The Parser's ParseComilationUnit() method returns the root AST node.
  5. 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:

  1. Console capture is enabled (if the "Show output in console" toggle is on), which redirects message() calls to the console pane.
  2. The Excel management codeunit is informed of the current program name and source code, enabling Excel integration during execution.
  3. The user's access level is loaded from the ToolBox User table and passed to the compiler registration module, which enforces permissions during execution.
  4. The license status is verified. In production SaaS environments, a valid license is required.
  5. The remote environment settings are configured (Application ID, Secret, and Tenant ID) for scripts that access data in other environments.
  6. 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() or Insert(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 procedure keyword before the main begin...end block.
  • Each procedure can have its own var section for local variables.
  • Procedures can accept parameters and return values.
  • Procedures can call other procedures defined in the same script.
  • The main begin...end block 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() and SetFilter() 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 than FindFirst() followed by Next().
  • Minimize CalcFields() calls. FlowFields require CalcFields() to retrieve their values, but each call involves a database query. Only call CalcFields() for fields you actually need.
  • Batch modifications. When updating many records, consider using ModifyAll() for single-field updates rather than looping with individual Modify() 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 of message().
  • Commit strategically. Use commit to save data in stages during long-running operations. This prevents losing all changes if a later operation fails.