uCalc API Version: 2.1.3-preview.2 Released: 6/16/2026
Warning
uCalc API Preview Release Notice:The documentation describes the intended behavior of the API. The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.
Project: Implementing a Custom Syntax Highlighter
Product:
Class:
A step-by-step project on building a static analysis tool (linter) to enforce coding standards on a custom scripting language using the uCalc Transformer.
Remarks
💡 Project: Implementing a Custom Syntax Highlighter
A syntax highlighter is a tool that analyzes source code and applies formatting (like colors and styles) to different parts of the syntax, such as keywords, strings, and comments. This project will guide you through building a simple but powerful syntax highlighter using the declarative rules of the uCalc.Transformer.
This is a classic real-world example of static analysis and demonstrates why the Transformer's token-aware engine is far superior to traditional regular expressions for this task.
The Goal
We will create a transformer that can scan a simple script and wrap different language elements in pseudo-HTML tags for styling:
- Keywords:
if,else,for,while→<keyword>if</keyword> - Strings:
"hello"→<string>"hello"</string> - Comments:
// a comment→<comment>// a comment</comment>
⚖️ Why uCalc Instead of Regex?
Attempting this with a series of Regex.Replace calls is extremely difficult and fragile. A regex for keywords would incorrectly match those words if they appeared inside a string literal or a comment. Handling these exceptions requires complex negative lookarounds that are hard to write and maintain.
uCalc's key advantage is token-awareness. The tokenizer runs first and identifies strings and comments as single, atomic units. This means a rule to find the keyword if will not even look inside a string like "An if statement", preventing incorrect matches by default.
Step 1: The Strategy - Categorize and Tag
Our approach will be to define a separate Pattern() rule for each syntax category we want to highlight. We will then use the Tag property to assign a unique integer ID to each rule. This allows us to programmatically identify which category a match belongs to.
- Define Categories: Create integer constants for our syntax types (e.g.,
TAG_KEYWORD = 1,TAG_STRING = 2). - Define Rules: Create a
Pattern()for each category and assign the appropriate tag using.SetTag(). Rule precedence (LIFO) is important here: more specific rules (like keywords) should be defined after more general ones (like generic identifiers). - Process Matches: After running
Find(), we will iterate through the Matches collection. For eachMatch, we will get its originatingRuleand check itsTagto determine how to format it.
Step 2: The Implementation
The full example below puts all these pieces together. It defines the rules, processes a sample code snippet, and then iterates through the matches to build a final, highlighted HTML-like string. This demonstrates a complete, working syntax highlighting engine.
Examples
A complete syntax highlighter that finds keywords, strings, and comments, and wraps them in pseudo-HTML tags for styling.
using uCalcSoftware;
var uc = new uCalc();
using (var t = new uCalc.Transformer()) {
// 1. Define integer constants for our syntax categories
var TAG_KEYWORD = 1;
var TAG_STRING = 2;
var TAG_COMMENT = 3;
// 2. Define the transformation rules and tag them
t.Pattern("{ if | else | for | while }").SetTag(TAG_KEYWORD);
t.Pattern("{@String}").SetTag(TAG_STRING);
t.Pattern("// {text}").SetTag(TAG_COMMENT);
// 3. Set the source code and run the find operation
string sourceCode = """
for (i=0; i<10; i++) {
s = "hello";
// comment
}
""";
t.Text = sourceCode;
t.Find();
// 4. Build the highlighted output string
string highlightedOutput = "";
var lastPos = 0;
foreach(var match in t.Matches) {
// Append the plain text between the last match and this one
highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos, match.StartPosition - lastPos);
// Get the tag and wrap the matched text accordingly
var tag = match.Rule.Tag;
if (tag == TAG_KEYWORD) {
highlightedOutput = highlightedOutput + "" + match.Text + " ";
} else if (tag == TAG_STRING) {
highlightedOutput = highlightedOutput + "" + match.Text + " ";
} else if (tag == TAG_COMMENT) {
highlightedOutput = highlightedOutput + "" + match.Text + " ";
} else {
highlightedOutput = highlightedOutput + match.Text; // No tag, append as-is
}
// Update the position for the next iteration
lastPos = match.EndPosition;
}
// Append any remaining text after the last match
highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos);
Console.WriteLine(highlightedOutput);
}
<keyword>for</keyword> (i=0; i<10; i++) {
s = <string>"hello"</string>;
<comment>// comment</comment>
} using uCalcSoftware; var uc = new uCalc(); using (var t = new uCalc.Transformer()) { // 1. Define integer constants for our syntax categories var TAG_KEYWORD = 1; var TAG_STRING = 2; var TAG_COMMENT = 3; // 2. Define the transformation rules and tag them t.Pattern("{ if | else | for | while }").SetTag(TAG_KEYWORD); t.Pattern("{@String}").SetTag(TAG_STRING); t.Pattern("// {text}").SetTag(TAG_COMMENT); // 3. Set the source code and run the find operation string sourceCode = """ for (i=0; i<10; i++) { s = "hello"; // comment } """; t.Text = sourceCode; t.Find(); // 4. Build the highlighted output string string highlightedOutput = ""; var lastPos = 0; foreach(var match in t.Matches) { // Append the plain text between the last match and this one highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos, match.StartPosition - lastPos); // Get the tag and wrap the matched text accordingly var tag = match.Rule.Tag; if (tag == TAG_KEYWORD) { highlightedOutput = highlightedOutput + "<keyword>" + match.Text + "</keyword>"; } else if (tag == TAG_STRING) { highlightedOutput = highlightedOutput + "<string>" + match.Text + "</string>"; } else if (tag == TAG_COMMENT) { highlightedOutput = highlightedOutput + "<comment>" + match.Text + "</comment>"; } else { highlightedOutput = highlightedOutput + match.Text; // No tag, append as-is } // Update the position for the next iteration lastPos = match.EndPosition; } // Append any remaining text after the last match highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos); Console.WriteLine(highlightedOutput); }
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
{
uCalc::Transformer t;
t.Owned(); // Causes t to be released when it goes out of scope
// 1. Define integer constants for our syntax categories
auto TAG_KEYWORD = 1;
auto TAG_STRING = 2;
auto TAG_COMMENT = 3;
// 2. Define the transformation rules and tag them
t.Pattern("{ if | else | for | while }").SetTag(TAG_KEYWORD);
t.Pattern("{@String}").SetTag(TAG_STRING);
t.Pattern("// {text}").SetTag(TAG_COMMENT);
// 3. Set the source code and run the find operation
string sourceCode = R"(for (i=0; i<10; i++) {
s = "hello";
// comment
})";
t.Text(sourceCode);
t.Find();
// 4. Build the highlighted output string
string highlightedOutput = "";
auto lastPos = 0;
for(auto match : t.Matches()) {
// Append the plain text between the last match and this one
highlightedOutput = highlightedOutput + sourceCode.substr(lastPos, match.StartPosition() - lastPos);
// Get the tag and wrap the matched text accordingly
auto tag = match.Rule().Tag();
if (tag == TAG_KEYWORD) {
highlightedOutput = highlightedOutput + "" + match.Text() + " ";
} else if (tag == TAG_STRING) {
highlightedOutput = highlightedOutput + "" + match.Text() + " ";
} else if (tag == TAG_COMMENT) {
highlightedOutput = highlightedOutput + "" + match.Text() + " ";
} else {
highlightedOutput = highlightedOutput + match.Text(); // No tag, append as-is
}
// Update the position for the next iteration
lastPos = match.EndPosition();
}
// Append any remaining text after the last match
highlightedOutput = highlightedOutput + sourceCode.substr(lastPos);
cout << highlightedOutput << endl;
}
}
<keyword>for</keyword> (i=0; i<10; i++) {
s = <string>"hello"</string>;
<comment>// comment</comment>
} #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; { uCalc::Transformer t; t.Owned(); // Causes t to be released when it goes out of scope // 1. Define integer constants for our syntax categories auto TAG_KEYWORD = 1; auto TAG_STRING = 2; auto TAG_COMMENT = 3; // 2. Define the transformation rules and tag them t.Pattern("{ if | else | for | while }").SetTag(TAG_KEYWORD); t.Pattern("{@String}").SetTag(TAG_STRING); t.Pattern("// {text}").SetTag(TAG_COMMENT); // 3. Set the source code and run the find operation string sourceCode = R"(for (i=0; i<10; i++) { s = "hello"; // comment })"; t.Text(sourceCode); t.Find(); // 4. Build the highlighted output string string highlightedOutput = ""; auto lastPos = 0; for(auto match : t.Matches()) { // Append the plain text between the last match and this one highlightedOutput = highlightedOutput + sourceCode.substr(lastPos, match.StartPosition() - lastPos); // Get the tag and wrap the matched text accordingly auto tag = match.Rule().Tag(); if (tag == TAG_KEYWORD) { highlightedOutput = highlightedOutput + "<keyword>" + match.Text() + "</keyword>"; } else if (tag == TAG_STRING) { highlightedOutput = highlightedOutput + "<string>" + match.Text() + "</string>"; } else if (tag == TAG_COMMENT) { highlightedOutput = highlightedOutput + "<comment>" + match.Text() + "</comment>"; } else { highlightedOutput = highlightedOutput + match.Text(); // No tag, append as-is } // Update the position for the next iteration lastPos = match.EndPosition(); } // Append any remaining text after the last match highlightedOutput = highlightedOutput + sourceCode.substr(lastPos); cout << highlightedOutput << endl; } }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
Using t As New uCalc.Transformer()
'// 1. Define integer constants for our syntax categories
Dim TAG_KEYWORD = 1
Dim TAG_STRING = 2
Dim TAG_COMMENT = 3
'// 2. Define the transformation rules and tag them
t.Pattern("{ if | else | for | while }").SetTag(TAG_KEYWORD)
t.Pattern("{@String}").SetTag(TAG_STRING)
t.Pattern("// {text}").SetTag(TAG_COMMENT)
'// 3. Set the source code and run the find operation
Dim sourceCode As String = "for (i=0; i<10; i++) {
s = ""hello"";
// comment
}"
t.Text = sourceCode
t.Find()
'// 4. Build the highlighted output string
Dim highlightedOutput As String = ""
Dim lastPos = 0
For Each match In t.Matches
'// Append the plain text between the last match and this one
highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos, match.StartPosition - lastPos)
'// Get the tag and wrap the matched text accordingly
Dim tag = match.Rule.Tag
If tag = TAG_KEYWORD Then
highlightedOutput = highlightedOutput + "" + match.Text + " "
ElseIf tag = TAG_STRING Then
highlightedOutput = highlightedOutput + "" + match.Text + " "
ElseIf tag = TAG_COMMENT Then
highlightedOutput = highlightedOutput + "" + match.Text + " "
Else
highlightedOutput = highlightedOutput + match.Text '// No tag, append as-is
End If
'// Update the position for the next iteration
lastPos = match.EndPosition
Next
'// Append any remaining text after the last match
highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos)
Console.WriteLine(highlightedOutput)
End Using
End Sub
End Module
<keyword>for</keyword> (i=0; i<10; i++) {
s = <string>"hello"</string>;
<comment>// comment</comment>
} Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() Using t As New uCalc.Transformer() '// 1. Define integer constants for our syntax categories Dim TAG_KEYWORD = 1 Dim TAG_STRING = 2 Dim TAG_COMMENT = 3 '// 2. Define the transformation rules and tag them t.Pattern("{ if | else | for | while }").SetTag(TAG_KEYWORD) t.Pattern("{@String}").SetTag(TAG_STRING) t.Pattern("// {text}").SetTag(TAG_COMMENT) '// 3. Set the source code and run the find operation Dim sourceCode As String = "for (i=0; i<10; i++) { s = ""hello""; // comment }" t.Text = sourceCode t.Find() '// 4. Build the highlighted output string Dim highlightedOutput As String = "" Dim lastPos = 0 For Each match In t.Matches '// Append the plain text between the last match and this one highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos, match.StartPosition - lastPos) '// Get the tag and wrap the matched text accordingly Dim tag = match.Rule.Tag If tag = TAG_KEYWORD Then highlightedOutput = highlightedOutput + "<keyword>" + match.Text + "</keyword>" ElseIf tag = TAG_STRING Then highlightedOutput = highlightedOutput + "<string>" + match.Text + "</string>" ElseIf tag = TAG_COMMENT Then highlightedOutput = highlightedOutput + "<comment>" + match.Text + "</comment>" Else highlightedOutput = highlightedOutput + match.Text '// No tag, append as-is End If '// Update the position for the next iteration lastPos = match.EndPosition Next '// Append any remaining text after the last match highlightedOutput = highlightedOutput + sourceCode.Substring(lastPos) Console.WriteLine(highlightedOutput) End Using End Sub End Module