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
- Drop a
TTMSFNCMemoCustomLanguageon the form. - Configure
ExtraKeysandTokenizer. - Add an entry to
TTMSFNCMemo.CustomLanguagesand assign the component. - 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;