Table of Contents

Code Generation

The TMS BCL code generation framework allows you to build Delphi source code programmatically. It follows a two-step approach:

  1. Build a code DOM (Document Object Model) — an in-memory object graph that represents the structure of a Delphi unit.
  2. Generate source code — pass the DOM to TDelphiCodeGenerator to produce formatted Delphi source code as a string.

The code DOM classes are declared in the Bcl.Code.MetaClasses unit. The generator is declared in the Bcl.​Code.​Delphi​Generator unit.

Code DOM Overview

The code DOM is a hierarchy of objects rooted at TCodeUnit, which represents a complete Delphi unit. All code elements inherit from the TCodeObject base class. The main classes are:

Statements within method bodies are represented by TCodeStatement descendants such as TCodeSnippet​Statement, TCodeCondition​Statement, TCodeForStatement, TCodeForEach​Statement, TCodeWhileStatement, TCodeTryFinally​Statement, and TCodeTryExcept​Statement.

Building a Unit

Create a TCodeUnit instance, set its name, and add units to the uses clause with TCodeUnit.UseUnit:

  CodeUnit := TCodeUnit.Create;
  try
    CodeUnit.Name := 'MyModule';
    CodeUnit.UseUnit('SysUtils');
    CodeUnit.UseUnit('Classes');
    CodeUnit.UseUnit('Generics.Collections', True); // Add to implementation uses
  finally
    CodeUnit.Free;
  end;

The UseUnit method avoids duplicates automatically. If a unit is already in the interface uses clause, calling UseUnit again has no effect. Pass True as the second parameter to add a unit to the implementation uses clause instead.

Declaring Types

Classes

Set TCodeTypeDeclaration.​IsClass to True and assign the ancestor type through TCodeTypeDeclaration.​Base​Type:

  CodeUnit := TCodeUnit.Create;
  try
    CodeUnit.Name := 'MyModule';

    MyClass := TCodeTypeDeclaration.Create;
    MyClass.Name := 'TCustomer';
    MyClass.IsClass := True;
    MyClass.BaseType.BaseType := 'TObject';
    CodeUnit._Types.Add(MyClass);
  finally
    CodeUnit.Free;
  end;

Add the type to the unit through the TCodeUnit._Types list. The unit owns the type and frees it when destroyed.

Records

Set TCodeTypeDeclaration.​IsRecord to True:

  CodeUnit := TCodeUnit.Create;
  try
    CodeUnit.Name := 'MyModule';

    MyRecord := TCodeTypeDeclaration.Create;
    MyRecord.Name := 'TPoint3D';
    MyRecord.IsRecord := True;
    MyRecord.AddField('X', 'Double', mvPublic);
    MyRecord.AddField('Y', 'Double', mvPublic);
    MyRecord.AddField('Z', 'Double', mvPublic);
    CodeUnit._Types.Add(MyRecord);
  finally
    CodeUnit.Free;
  end;

Interfaces

Set TCodeTypeDeclaration.​IsInterface to True and assign a base interface through TCodeTypeDeclaration.​Base​Type:

  CodeUnit := TCodeUnit.Create;
  try
    CodeUnit.Name := 'MyModule';

    Intf := TCodeTypeDeclaration.Create;
    Intf.Name := 'ILogger';
    Intf.IsInterface := True;
    Intf.BaseType.BaseType := 'IInterface';

    Intf.AddProcedure('Log', mvPublic).AddParameter('Msg', 'string');
    Intf.AddFunction('GetLevel', 'Integer', mvPublic);
    CodeUnit._Types.Add(Intf);
  finally
    CodeUnit.Free;
  end;

For interfaces with a GUID, set the TCodeTypeDeclaration.​Interface​Guid property.

Enumerations

Set TCodeTypeDeclaration.​IsEnum to True and add members by name. Each member is a TCodeTypeMember with only its Name set:

  CodeUnit := TCodeUnit.Create;
  try
    CodeUnit.Name := 'MyModule';

    EnumType := TCodeTypeDeclaration.Create;
    EnumType.Name := 'TOrderStatus';
    EnumType.IsEnum := True;
    EnumType.Members.Add(TCodeTypeMember.Create);
    EnumType.Members.Last.Name := 'osPending';
    EnumType.Members.Add(TCodeTypeMember.Create);
    EnumType.Members.Last.Name := 'osProcessing';
    EnumType.Members.Add(TCodeTypeMember.Create);
    EnumType.Members.Last.Name := 'osCompleted';
    CodeUnit._Types.Add(EnumType);
  finally
    CodeUnit.Free;
  end;

Adding Members

TCodeTypeDeclaration provides convenience methods for adding fields, properties, methods, constructors, and destructors. Each method returns the created instance for further configuration:

  MyClass := TCodeTypeDeclaration.Create;
  try
    MyClass.Name := 'TCustomer';
    MyClass.IsClass := True;
    MyClass.BaseType.BaseType := 'TObject';

    // Add private fields
    MyClass.AddField('FName', 'string', mvPrivate);
    MyClass.AddField('FAge', 'Integer', mvPrivate);
    MyClass.AddField('FActive', 'Boolean', mvPrivate);

    // Add a constructor and destructor
    MyClass.AddConstructor;
    MyClass.AddDestructor;

    // Add a procedure
    Method := MyClass.AddProcedure('Validate', mvPublic);
    Method.Directives := [mdVirtual];

    // Add a function
    Method := MyClass.AddFunction('ToString', 'string', mvPublic);
    Method.Directives := [mdOverride];

    // Add properties
    MyClass.AddProperty('Name', 'string', 'FName', 'FName', mvPublic);
    MyClass.AddProperty('Age', 'Integer', 'FAge', 'FAge', mvPublic);

    // Add a read-only property
    Prop := MyClass.AddProperty('Active', 'Boolean', 'FActive', '', mvPublic);
    Prop.HasSetter := False;
  finally
    MyClass.Free;
  end;

Member Visibility

All members accept a TMemberVisibility parameter that maps to Delphi visibility sections:

Value Delphi Section
mvPrivate private
mvProtected protected
mvPublic public
mvPublished published
mvStrictPrivate strict private
mvStrictProtected strict protected

Method Directives

Use the TCodeMemberMethod.​Directives property to apply method directives. The available directives in TCodeMethodDirective are: mdVirtual, mdDynamic, mdReintroduce, mdOverload, mdOverride, mdAbstract, and mdStatic.

Method Bodies

Add statements to a method body using TCodeMemberMethod.​Add​Snippet for raw code lines or TCodeMemberMethod.​Add​SnippetFmt for formatted strings. Declare local variables with TCodeMemberMethod.​DeclareVar:

  MyClass := TCodeTypeDeclaration.Create;
  try
    MyClass.Name := 'TCustomer';
    MyClass.IsClass := True;

    Method := MyClass.AddFunction('GetDisplayName', 'string', mvPublic);
    Method.AddParameter('AIncludeAge', 'Boolean');

    // Declare local variables
    Method.DeclareVar('Suffix', 'string');

    // Add statements using code snippets
    Method.AddSnippet('Suffix := ''''');
    Method.AddSnippetFmt('if %s then', ['AIncludeAge']);
    Method.AddSnippetFmt('  Suffix := '' ('' + IntToStr(%s) + '')''', ['FAge']);
    Method.AddSnippetFmt('Result := %s + Suffix', ['FName']);
  finally
    MyClass.Free;
  end;

Structured Statements

For structured control flow, use the dedicated statement classes instead of raw snippets. These classes produce properly formatted output when the generator's TDelphiCodeGenerator.​Structure​Statements property is enabled:

  Method := TCodeMemberMethod.Create;
  try
    // if..then..else statement
    CondStmt := TCodeConditionStatement.Create('FAge >= 18');
    CondStmt.TrueStatements.AddSnippet('Result := ''Adult''');
    CondStmt.FalseStatements.AddSnippet('Result := ''Minor''');
    Method.Statements.Add(CondStmt);

    // for loop statement
    ForStmt := TCodeForStatement.Create(
      'I',
      TCodeSnippetExpression.Create('0'),
      TCodeSnippetExpression.Create('Count - 1'),
      [TCodeSnippetStatement.Create('ProcessItem(I)')]
    );
    Method.Statements.Add(ForStmt);

    // try..finally statement
    TryStmt := TCodeTryFinallyStatement.Create(
      [TCodeSnippetStatement.Create('Stream := TFileStream.Create(FileName, fmOpenRead)')],
      [TCodeSnippetStatement.Create('Stream.Free')]
    );
    Method.Statements.Add(TryStmt);
  finally
    Method.Free;
  end;

The available structured statement classes are:

Parameter Modifiers

When adding parameters to a method via TCodeMemberMethod.​Add​Parameter, the default modifier is pmNone (value parameter). To create var or const parameters, set the TCodeParameter​Declaration.​Modifier property to pmVar or pmConst. Default values can be assigned through the TCodeParameter​Declaration.​Default​Value property.

Attributes and Comments

Custom Attributes

Add attributes to types and members using the AddAttribute method. Use TCodeAttribute​Declaration.​Add​RawArgument to pass arguments. The method returns the attribute instance for chaining:

  MyClass := TCodeTypeDeclaration.Create;
  try
    MyClass.Name := 'TCustomer';
    MyClass.IsClass := True;

    // Add attribute to the type
    MyClass.AddAttribute('Entity');
    MyClass.AddAttribute('Table').AddRawArgument('''customers''');

    // Add attribute to a field
    Field := MyClass.AddField('FName', 'string', mvPrivate);
    Field.AddAttribute('Column').AddRawArgument('''customer_name''');
  finally
    MyClass.Free;
  end;

Comments

Add comments to any type member through the TCodeTypeMember.​Comments list. The TCodeComment class supports three styles via TCommentStyle:

Generating Source Code

Pass the completed TCodeUnit to TDelphiCodeGenerator.​Generate​Code​From​Unit to produce the Delphi source code:

  CodeUnit := TCodeUnit.Create;
  try
    CodeUnit.Name := 'MyModule';
    // ... build the unit ...

    Generator := TDelphiCodeGenerator.Create;
    try
      Source := Generator.GenerateCodeFromUnit(CodeUnit);
      // Source now contains the complete Delphi source code
    finally
      Generator.Free;
    end;
  finally
    CodeUnit.Free;
  end;

Generator Options

TDelphiCodeGenerator provides several properties to control the output format:

Property Description
SortMembers Sort type members alphabetically by name.
SortUnits Sort units in the uses clause alphabetically.
LineBeforeComplex​Members Insert a blank line before members that have comments or attributes.
StructureStatements Output structured statements with proper formatting and semicolons.
ReservedWordMode Control how Delphi reserved words used as identifiers are handled.

Reserved Word Handling

When an identifier matches a Delphi reserved word, the generator can adjust it based on the TReservedWordMode setting:

  • Underline — Appends an underscore (e.g., type becomes type_).
  • Ampersand — Prefixes with ampersand (e.g., type becomes &type).
  • Ignore — Outputs the identifier unchanged.

Use TDelphiCodeGenerator.​IsReserved​Word to check whether a string is a Delphi reserved word.

Complete Example

The following example builds a complete unit with a class, constructor, method with local variables, and properties, then generates the source code:

  CodeUnit := TCodeUnit.Create;
  try
    CodeUnit.Name := 'CustomerModel';
    CodeUnit.UseUnit('SysUtils');
    CodeUnit.UseUnit('Classes');

    // Create the TCustomer class
    Customer := TCodeTypeDeclaration.Create;
    Customer.Name := 'TCustomer';
    Customer.IsClass := True;
    Customer.BaseType.BaseType := 'TPersistent';
...
  // Add fields
  Customer.AddField('FName', 'string', mvPrivate);
  Customer.AddField('FEmail', 'string', mvPrivate);
  Customer.AddField('FAge', 'Integer', mvPrivate);

  // Add constructor with body
  Method := Customer.AddConstructor;
  Method.AddParameter('AName', 'string');
  Method.AddParameter('AEmail', 'string');
  Method.AddSnippet('inherited Create');
  Method.AddSnippet('FName := AName');
  Method.AddSnippet('FEmail := AEmail');
...
  // Add a function with local variables and logic
  Method := Customer.AddFunction('GetSummary', 'string', mvPublic);
  Method.DeclareVar('AgeStr', 'string');
  Method.AddSnippet('AgeStr := IntToStr(FAge)');
  Method.AddSnippet('Result := FName + '' <'' + FEmail + ''> (Age: '' + AgeStr + '')''');

  // Add properties
  Customer.AddProperty('Name', 'string', 'FName', 'FName', mvPublished);
  Customer.AddProperty('Email', 'string', 'FEmail', 'FEmail', mvPublished);
  Customer.AddProperty('Age', 'Integer', 'FAge', 'FAge', mvPublished);

  CodeUnit._Types.Add(Customer);
...
    // Generate the source code
    Generator := TDelphiCodeGenerator.Create;
    try
      Generator.SortMembers := True;
      Generator.StructureStatements := True;
      Result := Generator.GenerateCodeFromUnit(CodeUnit);

    finally
      Generator.Free;
    end;
  finally
    CodeUnit.Free;
  end;

This is the generated source code output:

unit CustomerModel;

interface

uses
  SysUtils,
  Classes;

type
  TCustomer = class;

  TCustomer = class(TPersistent)
  private
    FAge: Integer;
    FEmail: string;
    FName: string;
  public
    constructor Create(AName: string; AEmail: string);
    function GetSummary: string;
  published
    property Age: Integer read FAge write FAge;
    property Email: string read FEmail write FEmail;
    property Name: string read FName write FName;
  end;

implementation

{ TCustomer }

constructor TCustomer.Create(AName: string; AEmail: string);
begin
  inherited Create;
  FName := AName;
  FEmail := AEmail;
end;

function TCustomer.GetSummary: string;
var
  AgeStr: string;
begin
  AgeStr := IntToStr(FAge);
  Result := FName + ' <' + FEmail + '> (Age: ' + AgeStr + ')';
end;

end.