Skip to content

Compiler

Edgar Mesquita edited this page Jan 31, 2026 · 10 revisions

The Compiler (CSharpToJs)

The compiler is the core component that enables the magic of eQuantic.UI. It transforms C# semantics into efficient and readable TypeScript code.

🛠️ Compiler Components

1. TypeScriptEmitter

The TypeScriptEmitter is the entry point for generating .ts files. It organizes imports, defines classes, and uses the CSharpToJsConverter to convert method bodies.

2. CSharpToJsConverter

A implementation based on the Strategy pattern that traverses the Roslyn syntax tree (AST).

  • Each type of C# expression or statement has a dedicated strategy (e.g., BinaryExpressionStrategy, IfStatementStrategy).

3. SourceMapGenerator

Generates standard V3 Source Maps with Base64 VLQ encoding, mapping generated JavaScript/TypeScript back to the original .cs or .eqx source lines.

4. Identifier Heuristics

The converter applies intelligent heuristics to decide how to map variable names:

  • C# properties and fields are mapped to this.propertyName in JS.
  • Local variables and parameters retain their original names.
  • System methods like Console.WriteLine are automatically mapped to console.log.

🔄 Supported Strategies

Currently, the compiler supports a wide range of C# constructs:

  • Expressions: Arithmetic, Logical, Ternary, String Interpolation, Null-coalescing (??), Conditional Access (?., ?[])
  • Control Flow: if, switch, for, foreach, while, do-while, break, continue, throw
  • Modern Patterns: Full support for Recursive, Property, Positional, Relational, and Logical patterns (C# 9.0 - 12.0)
  • Resource Management: Support for using statements and using var declarations
  • Exceptions: Full support for try-catch-finally and throw statements (Exception → Error)
  • Indexes and Ranges: Support for index-from-end operator (array[^1]array[array.length - 1])
  • String Methods: Instance methods (Split, Replace, StartsWith, EndsWith, Contains, Substring, IndexOf, LastIndexOf, PadLeft, PadRight, TrimStart, TrimEnd, Insert, Remove, ToCharArray) and static methods (IsNullOrEmpty, IsNullOrWhiteSpace, Join, Concat, Compare, Equals, Format)
  • Number Methods: int.Parse, double.Parse, float.Parse, decimal.Parse, long.Parse, int.TryParse, double.TryParse
  • List Methods: Add, AddRange, Insert, InsertRange, Remove, RemoveAt, RemoveRange, RemoveAll, Clear, IndexOf, LastIndexOf, Find, FindIndex, FindLast, FindLastIndex, FindAll, Exists, TrueForAll, Sort, ForEach, GetRange, CopyTo, BinarySearch
  • Array Static Methods: Full support for Array static methods
    • Array.Sort(array)array.sort() - Sort array in place
    • Array.Sort(array, comparison)array.sort(comparison) - Sort with custom comparer
    • Array.Reverse(array)array.reverse() - Reverse array in place
    • Array.Find(array, predicate)array.find(predicate) - Find first matching element
    • Array.FindIndex(array, predicate)array.findIndex(predicate) - Find index of first match
    • Array.FindAll(array, predicate)array.filter(predicate) - Find all matching elements
    • Array.IndexOf(array, value)array.indexOf(value) - Find index of value
    • Array.LastIndexOf(array, value)array.lastIndexOf(value) - Find last index of value
    • Array.Exists(array, predicate)array.some(predicate) - Check if any element matches
    • Array.TrueForAll(array, predicate)array.every(predicate) - Check if all elements match
    • Array.Clear(array)array.splice(0) - Clear all elements
    • Array.Resize(ref array, size)array.length = size - Resize array
  • Enum Methods: Full enum operations support
    • Enum.Parse<T>(string)parseEnum(value, EnumType) (case-insensitive)
    • Enum.TryParse<T>(string, out var result)(result = parseEnum(value, EnumType), result !== undefined)
    • Enum.GetValues<T>()Object.values(EnumType) - Get all enum values
    • Enum.GetNames<T>()Object.keys(EnumType) - Get all enum member names
    • Enum.IsDefined(typeof(T), value)(EnumType[value] !== undefined) - Validate enum value
  • Dictionary Methods: Complete Dictionary/IDictionary support
    • ContainsKey(key)(key in dict) - Check if key exists
    • TryGetValue(key, out var value)(value = dict[key]) !== undefined - Safe value retrieval
    • Add(key, value)dict[key] = value - Add or update entry
    • Remove(key)delete dict[key] - Remove entry
    • Clear()Object.keys(dict).forEach(k => delete dict[k]) - Remove all entries
    • Keys (property) → Object.keys(dict) - Get all keys as array
    • Values (property) → Object.values(dict) - Get all values as array
  • LINQ: Direct conversion of LINQ methods to JS equivalents:
    • Projection: Selectmap, SelectManyflatMap
    • Filtering: Wherefilter, Distinct[...new Set()]
    • Ordering: OrderBy/OrderByDescendingsort, Reverse[...arr].reverse()
    • Partitioning: Skipslice(n), Takeslice(0, n)
    • Element: First/FirstOrDefaultfind/[0], Last/LastOrDefaultarr[arr.length-1], Single/SingleOrDefaultfind/[0]
    • Quantifiers: Anysome/length > 0, Allevery, Containsincludes
    • Aggregation: Countlength/filter().length, Sumreduce((a,b) => a+b, 0), Averagereduce()/length, MinMath.min(...), MaxMath.max(...)
    • Set Operations:
      • Concat(other)[...source, ...other] - Concatenate two sequences
      • Union(other)[...new Set([...source, ...other])] - Unique elements from both sequences
      • Intersect(other)[...new Set(source)].filter(x => other.includes(x)) - Common elements
      • Except(other)[...new Set(source)].filter(x => !other.includes(x)) - Elements in source but not in other
    • Type Filtering:
      • Cast<T>() → passthrough (JavaScript is dynamically typed)
      • OfType<T>()filter(x => typeof x === 'type') for primitives, filter(x => x instanceof Type) for objects
  • Async/Await: Mapping of Task to Promise and native await support.

📝 Conversion Example

C# Source:

private void Increment() {
    Count++;
    if (Count > 10) Console.WriteLine("Max reached");
}

TypeScript Output:

increment() {
    this.count++;
    if (this.count > 10) console.log("Max reached");
}

🎯 Advanced Features Examples

Enum Operations

C# Source:

public enum OrderStatus { Pending, Processing, Shipped, Delivered }

private void HandleStatusChange(string input)
{
    // Parse enum from string (case-insensitive)
    if (Enum.TryParse<OrderStatus>(input, out var status))
    {
        Console.WriteLine($"Status changed to: {status}");
    }

    // Get all enum values for dropdown
    var allStatuses = Enum.GetValues<OrderStatus>();
    foreach (var s in allStatuses)
    {
        Console.WriteLine($"Available status: {s}");
    }

    // Validate enum value
    if (Enum.IsDefined(typeof(OrderStatus), "Shipped"))
    {
        Console.WriteLine("Valid status");
    }
}

TypeScript Output:

handleStatusChange(input: string) {
    // Parse with TryParse
    if ((status = parseEnum(input, OrderStatus), status !== undefined)) {
        console.log(`Status changed to: ${status}`);
    }

    // Get all values
    const allStatuses = Object.values(OrderStatus);
    for (const s of allStatuses) {
        console.log(`Available status: ${s}`);
    }

    // Validate
    if ((OrderStatus['Shipped'] !== undefined)) {
        console.log('Valid status');
    }
}

Dictionary Operations

C# Source:

private Dictionary<string, int> _settings = new();

private void ManageSettings()
{
    // Add entries
    _settings.Add("timeout", 5000);
    _settings.Add("retries", 3);

    // Check existence
    if (_settings.ContainsKey("timeout"))
    {
        var timeout = _settings["timeout"];
        Console.WriteLine($"Timeout: {timeout}");
    }

    // Safe retrieval
    if (_settings.TryGetValue("maxItems", out var max))
    {
        Console.WriteLine($"Max: {max}");
    }

    // Iterate keys
    foreach (var key in _settings.Keys)
    {
        Console.WriteLine($"{key} = {_settings[key]}");
    }

    // Clear all
    _settings.Clear();
}

TypeScript Output:

private _settings: Record<string, number> = {};

manageSettings() {
    // Add entries
    this._settings['timeout'] = 5000;
    this._settings['retries'] = 3;

    // Check existence
    if (('timeout' in this._settings)) {
        const timeout = this._settings['timeout'];
        console.log(`Timeout: ${timeout}`);
    }

    // Safe retrieval
    if ((max = this._settings['maxItems']) !== undefined) {
        console.log(`Max: ${max}`);
    }

    // Iterate keys
    for (const key of Object.keys(this._settings)) {
        console.log(`${key} = ${this._settings[key]}`);
    }

    // Clear all
    Object.keys(this._settings).forEach(k => delete this._settings[k]);
}

LINQ Set Operations

C# Source:

private void ProcessCollections()
{
    var list1 = new[] { 1, 2, 3, 4 };
    var list2 = new[] { 3, 4, 5, 6 };

    // Concatenate two lists
    var combined = list1.Concat(list2);
    // Result: [1, 2, 3, 4, 3, 4, 5, 6]

    // Union - unique elements from both
    var union = list1.Union(list2);
    // Result: [1, 2, 3, 4, 5, 6]

    // Intersect - common elements
    var common = list1.Intersect(list2);
    // Result: [3, 4]

    // Except - elements in list1 but not in list2
    var difference = list1.Except(list2);
    // Result: [1, 2]

    // Complex filtering with set operations
    var activeUsers = GetActiveUsers();
    var premiumUsers = GetPremiumUsers();

    // Users that are both active AND premium
    var activePremium = activeUsers.Intersect(premiumUsers);

    // Users that are active but NOT premium
    var activeFree = activeUsers.Except(premiumUsers);
}

TypeScript Output:

processCollections() {
    const list1 = [1, 2, 3, 4];
    const list2 = [3, 4, 5, 6];

    // Concatenate
    const combined = [...list1, ...list2];

    // Union (with Set to remove duplicates)
    const union = [...new Set([...list1, ...list2])];

    // Intersect (common elements)
    const common = [...new Set(list1)].filter(x => list2.includes(x));

    // Except (difference)
    const difference = [...new Set(list1)].filter(x => !list2.includes(x));

    // Complex filtering
    const activeUsers = this.getActiveUsers();
    const premiumUsers = this.getPremiumUsers();

    const activePremium = [...new Set(activeUsers)].filter(x => premiumUsers.includes(x));
    const activeFree = [...new Set(activeUsers)].filter(x => !premiumUsers.includes(x));
}

Array Static Methods

C# Source:

private void ProcessArrayOperations()
{
    var numbers = new[] { 5, 2, 8, 1, 9 };
    var items = new[] { "apple", "banana", "cherry" };

    // Sort array in place
    Array.Sort(numbers);
    // Result: [1, 2, 5, 8, 9]

    // Sort with custom comparison
    Array.Sort(items, (a, b) => b.Length - a.Length);
    // Result: ["banana", "cherry", "apple"]

    // Reverse array
    Array.Reverse(numbers);
    // Result: [9, 8, 5, 2, 1]

    // Find operations
    var firstEven = Array.Find(numbers, n => n % 2 == 0);
    var firstEvenIndex = Array.FindIndex(numbers, n => n % 2 == 0);
    var allEvens = Array.FindAll(numbers, n => n % 2 == 0);

    // Search operations
    var index = Array.IndexOf(numbers, 5);
    var lastIndex = Array.LastIndexOf(numbers, 5);

    // Check operations
    var hasEven = Array.Exists(numbers, n => n % 2 == 0);
    var allPositive = Array.TrueForAll(numbers, n => n > 0);

    // Clear and resize
    Array.Clear(numbers);
    Array.Resize(ref items, 5);  // Expand to 5 elements
}

TypeScript Output:

processArrayOperations() {
    const numbers = [5, 2, 8, 1, 9];
    const items = ["apple", "banana", "cherry"];

    // Sort
    numbers.sort();

    // Sort with comparison
    items.sort((a, b) => b.length - a.length);

    // Reverse
    numbers.reverse();

    // Find operations
    const firstEven = numbers.find(n => n % 2 == 0);
    const firstEvenIndex = numbers.findIndex(n => n % 2 == 0);
    const allEvens = numbers.filter(n => n % 2 == 0);

    // Search operations
    const index = numbers.indexOf(5);
    const lastIndex = numbers.lastIndexOf(5);

    // Check operations
    const hasEven = numbers.some(n => n % 2 == 0);
    const allPositive = numbers.every(n => n > 0);

    // Clear and resize
    numbers.splice(0);
    items.length = 5;
}

LINQ Type Filtering (Cast & OfType)

C# Source:

private void FilterByType()
{
    // Mixed type collection
    object[] mixed = new object[] { 1, "hello", 2, "world", 3.14, true };

    // Cast<T>() - assumes all elements are of type T (passthrough in JS)
    var assumedStrings = mixed.Cast<string>();

    // OfType<T>() - filters to only elements of type T
    var onlyStrings = mixed.OfType<string>();
    // Result: ["hello", "world"]

    var onlyNumbers = mixed.OfType<int>();
    // Result: [1, 2]

    // Works with custom classes too
    var shapes = new object[] { new Circle(), new Square(), new Circle() };
    var circles = shapes.OfType<Circle>();
    // Result: [Circle, Circle]

    // Primitive type filtering
    var primitives = new object[] { 1, "text", 2.5, true, null };
    var strings = primitives.OfType<string>();  // ["text"]
    var numbers = primitives.OfType<double>();  // [1, 2.5]
    var booleans = primitives.OfType<bool>();   // [true]
}

TypeScript Output:

filterByType() {
    // Mixed type collection
    const mixed = [1, "hello", 2, "world", 3.14, true];

    // Cast - passthrough (JS is dynamically typed)
    const assumedStrings = mixed;

    // OfType - filter by typeof for primitives
    const onlyStrings = mixed.filter(x => typeof x === 'string');
    // Result: ["hello", "world"]

    const onlyNumbers = mixed.filter(x => typeof x === 'number');
    // Result: [1, 2, 3.14]

    // OfType - filter by instanceof for objects
    const shapes = [new Circle(), new Square(), new Circle()];
    const circles = shapes.filter(x => x instanceof Circle);
    // Result: [Circle, Circle]

    // Primitive filtering
    const primitives = [1, "text", 2.5, true, null];
    const strings = primitives.filter(x => typeof x === 'string');  // ["text"]
    const numbers = primitives.filter(x => typeof x === 'number');  // [1, 2.5]
    const booleans = primitives.filter(x => typeof x === 'boolean'); // [true]
}

Clone this wiki locally