Table of Contents

Custom Language

TTMSFNCMemoCustomLanguage is a companion component that defines a syntax-highlighting language for TTMSFNCMemo. It maps to the Monarch tokenizer used by Monaco Editor.


Setup overview

  1. Drop a TTMSFNCMemoCustomLanguage on the form.
  2. Configure ExtraKeys and Tokenizer.
  3. Add an entry to TTMSFNCMemo.CustomLanguages and assign the component.
  4. Set TTMSFNCMemo.Language := mlCustom.
TMSFNCMemo1.CustomLanguages.Add.Language := TMSFNCMemoCustomLanguage1;
TMSFNCMemo1.Language := mlCustom;
// Optionally name the language:
// TMSFNCMemo1.CustomLanguageID := 'my-java';

ExtraKeys

ExtraKeys defines named keyword lists and regular-expression helpers that the tokenizer rules can reference with the @name syntax.

Each TTMSFNCMemoCustomLanguageKey item has:

Property Description
Name The reference name used in tokenizer rules (e.g. 'keywords', 'symbols').
ValueList A TStringList of literal strings for keyword matching.
ValueRegex A regex string for pattern matching.
// Keyword lists — use ValueList
TMSFNCMemoCustomLanguage1.ExtraKeys.Add('keywords',
  ['if', 'else', 'while', 'for', 'class', 'return', 'void', 'true', 'false']);

TMSFNCMemoCustomLanguage1.ExtraKeys.Add('typeKeywords',
  ['boolean', 'int', 'double', 'float', 'string']);

// Regex-valued keys — use ValueRegex
TMSFNCMemoCustomLanguage1.ExtraKeys.Add('symbols',  '[=><!~?:&|+\-*\/\^%]+');
TMSFNCMemoCustomLanguage1.ExtraKeys.Add('escapes',
  '\\(?:[abfnrtv\\"''']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4})');

Tokenizer

The Tokenizer collection contains one or more named states. Each state has Rules that map regex patterns to token types. The tokenizer starts in the root state.

Adding rules

TTMSFNCMemoCustomLanguageRule has:

Property Description
Regex Regular expression to match.
Include Reference another state with @stateName (no Regex needed).
Action.Token Token type string (e.g. 'keyword', 'string', 'number').
Action.Cases A guard-to-token map (e.g. @keywords'keyword').
Action.Next Transition to another state ('@string', '@pop', '@push').
Action.Bracket lbkOpen or lbkClose for bracket matching.
Action.Group Multiple token actions for a grouped regex (multiple captures).
Action.LogMessage Debug message emitted when the rule matches.

Shorthand Add overloads on TTMSFNCMemoCustomLanguageRules:

root.Rules.Add('[A-Z][\w]*',         'type.identifier');          // regex + token
root.Rules.Add('\/\/.*$',            'comment', '@comment');       // + next state
root.Rules.Add('"', 'string.quote', lbkOpen, '@string');          // + bracket + next

Full Java-style tokenizer example

// Step 1 — Configure TCustomLanguage (drop on form or create in code)
procedure TForm1.SetupCustomLanguage;
var
  CustomLanguage: TTMSFNCMemoCustomLanguage;
  root, comment, str, whitespace: TTMSFNCMemoCustomLanguageTokenizerItem;
  r: TTMSFNCMemoCustomLanguageRule;
begin
  // --- Extra keys: keyword lists and regex helpers ---
  CustomLanguage.ExtraKeys.Add('keywords',
    ['abstract', 'break', 'case', 'catch', 'class', 'const',
     'continue', 'do', 'else', 'enum', 'finally', 'for',
     'goto', 'if', 'interface', 'new', 'private', 'protected',
     'public', 'return', 'static', 'super', 'switch', 'this',
     'throw', 'try', 'void', 'while', 'true', 'false']);

  CustomLanguage.ExtraKeys.Add('typeKeywords',
    ['boolean', 'byte', 'char', 'double', 'float', 'int', 'long', 'short']);

  CustomLanguage.ExtraKeys.Add('operators',
    ['=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=',
     '&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%']);

  // Regex-valued keys
  CustomLanguage.ExtraKeys.Add('symbols',  '[=><!~?:&|+\-*\/\^%]+');
  CustomLanguage.ExtraKeys.Add('escapes',
    '\\(?:[abfnrtv\\"''']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})');

  // --- Tokenizer rules ---
  root := CustomLanguage.Tokenizer.Add('root');

  // Identifiers and keywords
  r := root.Rules.Add;
  r.Regex := '[a-z_$][\w$]*';
  r.Action.Cases.Add('@typeKeywords', 'keyword');
  r.Action.Cases.Add('@keywords',     'keyword');
  r.Action.Cases.Add('@default',      'identifier');

  root.Rules.Add('[A-Z][\w\$]*', 'type.identifier');

  // Whitespace
  root.Rules.Add.Include := '@whitespace';

  // Delimiters and operators
  root.Rules.Add('[{}()\[\]]', '@brackets');
  root.Rules.Add('[<>](?!@symbols)', '@brackets');
  r := root.Rules.Add;
  r.Regex := '@symbols';
  r.Action.Cases.Add('@operators', 'operator');
  r.Action.Cases.Add('@default',   '');

  // Numbers
  root.Rules.Add('\d*\.\d+([eE][\-+]?\d+)?', 'number.float');
  root.Rules.Add('0[xX][0-9a-fA-F]+',         'number.hex');
  root.Rules.Add('\d+',                         'number');

  // Strings
  root.Rules.Add('"([^"\\]|\\.)*$', 'string.invalid');
  r := root.Rules.Add;
  r.Regex            := '"';
  r.Action.Token     := 'string.quote';
  r.Action.Bracket   := lbkOpen;
  r.Action.Next      := '@string';

  // Comment block
  comment := CustomLanguage.Tokenizer.Add('comment');
  comment.Rules.Add('[^\/*]+',  'comment');
  comment.Rules.Add('\/\*',     'comment', '@push');
  comment.Rules.Add('\*\/',     'comment', '@pop');
  comment.Rules.Add('[\/*]',    'comment');

  // String state
  str := CustomLanguage.Tokenizer.Add('string');
  str.Rules.Add('[^\\"]+',  'string');
  str.Rules.Add('@escapes', 'string.escape');
  str.Rules.Add('\\.',      'string.invalid');
  r := str.Rules.Add;
  r.Regex          := '"';
  r.Action.Token   := 'string.quote';
  r.Action.Bracket := lbkClose;
  r.Action.Next    := '@pop';

  // Whitespace state
  whitespace := CustomLanguage.Tokenizer.Add('whitespace');
  whitespace.Rules.Add('[ \t\r\n]+', 'white');
  whitespace.Rules.Add('\/\*',        'comment', '@comment');
  whitespace.Rules.Add('\/\/.*$',     'comment');
end;

// Step 2 — Assign to TMemo
procedure TForm1.ApplyCustomLanguage;
var
  Memo: TTMSFNCMemo;
  CustomLanguage: TTMSFNCMemoCustomLanguage;
begin
  Memo.CustomLanguages.Add.Language := CustomLanguage;
  Memo.Language         := mlCustom;
  // Optionally override the language ID
  // Memo.CustomLanguageID := 'my-java';
end;

// Save / load the language definition
procedure TForm1.SaveLanguage;
begin
  CustomLanguage.SaveSettingsToFile('C:\Config\my-lang.json');
end;

procedure TForm1.LoadLanguage;
begin
  CustomLanguage.LoadSettingsFromFile('C:\Config\my-lang.json');
end;

TTMSFNCMemoCustomLanguage properties

Property Description
LanguageID The language identifier. Defaults to the component name if empty.
DefaultToken Token type used when no rule matches.
IgnoreCase Case-insensitive regex matching. Default False.
IncludeLF Append \n at the end of each tokenized line. Default False.
Unicode Unicode-aware matching.
Brackets Bracket definitions for automatic brace matching.
ExtraKeys Named keyword lists and regex helpers.
Tokenizer Tokenizer state machine.

Brackets

Define matching bracket pairs so the editor can highlight them:

TMSFNCMemoCustomLanguage1.Brackets.Add('{', '}', lbtCurly);
TMSFNCMemoCustomLanguage1.Brackets.Add('[', ']', lbtSquare);
TMSFNCMemoCustomLanguage1.Brackets.Add('(', ')', lbtParentesis);

Save and load a language definition

Use SaveSettingsToFile / LoadSettingsFromFile to persist the language definition via TTMSFNCPersistence JSON serialization:

// Save
TMSFNCMemoCustomLanguage1.SaveSettingsToFile('C:\Config\my-lang.json');

// Load
TMSFNCMemoCustomLanguage1.LoadSettingsFromFile('C:\Config\my-lang.json');

Combining keywords, tokenizer rules, and bracket definitions

Add keyword lists, connect them with tokenizer rules, and define matching bracket pairs in one setup block:

procedure TForm1.FormCreate(Sender: TObject);
var
  root: TTMSFNCMemoCustomLanguageState;
begin
  TMSFNCMemoCustomLanguage1.ExtraKeys.Add('keywords',
    ['if', 'else', 'while', 'return']);
  TMSFNCMemoCustomLanguage1.ExtraKeys.Add('symbols', '[=><!~?:&|+\-*\/]+');

  TMSFNCMemoCustomLanguage1.Brackets.Add('{', '}', lbtCurly);
  TMSFNCMemoCustomLanguage1.Brackets.Add('(', ')', lbtParentesis);

  root := TMSFNCMemoCustomLanguage1.Tokenizer.Add('root');
  root.Rules.Add('[a-zA-Z_]\w*', '@keywords', 'keyword', 'identifier');
  root.Rules.Add('@symbols', 'operator');
  root.Rules.Add('[0-9]+', 'number');

  TMSFNCMemo1.CustomLanguages.Add.Language := TMSFNCMemoCustomLanguage1;
  TMSFNCMemo1.Language := mlCustom;
end;

See also