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.

  1. Define Categories: Create integer constants for our syntax types (e.g., TAG_KEYWORD = 1, TAG_STRING = 2).
  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).
  3. Process Matches: After running Find(), we will iterate through the Matches collection. For each Match, we will get its originating Rule and check its Tag to 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 + "<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);
}
				
			
<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;
   }
}
				
			
<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
				
			
<keyword>for</keyword> (i=0; i<10; i++) {
  s = <string>"hello"</string>;
  <comment>// comment</comment>
}