Skip to content

Mapping

Tristin Porter edited this page Jan 10, 2026 · 4 revisions

Mapping in CDTk

CDTk provides a powerful yet intuitive system for transforming Abstract Syntax Trees (ASTs) into target code. This process is the cornerstone of compilers, transpilers, and other language tools. Here, you’ll find everything you need to understand and implement mapping effectively in your projects.


Table of Contents

  1. What Is Mapping?
  2. Key Features
  3. Creating and Using a MapSet
  4. Template-Based Code Generation
  5. Advanced Mapping Techniques
  6. Error Handling and Diagnostics
  7. Integrating Mapping with Compilers
  8. End-to-End Mapping Example
  9. Best Practices
  10. Next Steps

What Is Mapping?

Mapping is the process of converting an AST into a structured output, such as source code or data, using predefined patterns.

  • Map: Defines how a specific AST node type is rendered.
  • MapSet: Groups multiple map rules into one cohesive framework.

Example

var maps = new MapSet
{
    new Map("AssignmentNode", "let {variable} = {value};")
};

For this AST:

{
    "type": "AssignmentNode",
    "fields": {
        "variable": "x",
        "value": 42
    }
}

The resulting output will be:

let x = 42;

Key Features

  1. Template-Driven Mapping: Use simple {fieldName} placeholders for field substitution.
  2. Fallback Safety: Create default rules to prevent errors during unmapped cases.
  3. Dynamic Mapping with Context: Process runtime-specific details flexibly.
  4. Multi-Target Support: Generate output for multiple languages or formats simultaneously.

Creating and Using a MapSet

A MapSet holds all mappings for your project and ensures a cohesive code-generation strategy.

Syntax

Define a MapSet and populate it with Map rules for specific node types:

var mapping = new MapSet
{
    new Map("AssignmentNode", "let {variable} = {value};"),
    new Map("BinaryOpNode", "({left} {operator} {right})"),
    new Map("IfNode", "if ({condition}) {body}")
};

Template-Based Code Generation

Dynamic Placeholders

Placeholders ({fieldName}) correspond to fields in the AST:

new Map("BinaryOpNode", "({left} {operator} {right})");

Given this AST:

{
    "type": "BinaryOpNode",
    "fields": {
        "left": "x",
        "operator": "+",
        "right": "y"
    }
}

The resulting output is:

(x + y)

Traversing Nested Fields

Templates automatically resolve nested fields in the AST:

new Map("FunctionCallNode", "{functionName}({arguments})");

For AST:

{
    "type": "FunctionCallNode",
    "fields": {
        "functionName": "print",
        "arguments": "42"
    }
}

The output is:

print(42)

Advanced Mapping Techniques

Fallback Mapping

Guarantee all nodes are handled by defining a fallback rule:

new Map("UnknownNode", "// TODO: Handle {type}");

This prevents runtime errors and flags missing mappings.


Handling Variations

Use separate maps for different node types:

new Map("UnaryOpNode", "{operator}{operand}");
new Map("BinaryOpNode", "({left} {operator} {right})");

Inline, Dynamic Context

Introduce context-specific behaviors into templates dynamically:

new Map("VariableDeclaration", "let {name}: {type} = {value};")

Error Handling and Diagnostics

CDTk’s built-in diagnostics track mapping errors and provide detailed warnings.

Example

Log errors with helpful feedback:

diagnostics.Add(
    Stage.Mapping,
    DiagnosticLevel.Error,
    "Mapping failed at node {type}",
    SourceSpan.Unknown
);

Avoiding Redundant Warnings

Enable deduplication to reduce diagnostic clutter:

diagnostics.Deduplicate = true;

Integrating Mapping with Compilers

Mappings plug seamlessly into the compilation pipeline:

  • Single Target Compilation:
var compiler = new Compiler()
    .WithTokens(tokens)
    .WithRules(rules)
    .WithTarget(mapSet)
    .Build();

var result = compiler.Compile(inputSource);
Console.WriteLine(result.Output);
  • Multi-Target Compilation:
var compiler = new Compiler()
    .WithTokens(tokens)
    .WithRules(rules)
    .WithTargets(
        ("LanguageA", mapA),
        ("LanguageB", mapB)
    )
    .Build();

var result = compiler.Compile(inputSource);
Console.WriteLine(result.Outputs["LanguageA"]);
Console.WriteLine(result.Outputs["LanguageB"]);

End-to-End Mapping Example

Here’s a complete example of mapping a small language.

Mapping Rules

var mapSet = new MapSet
{
    new Map("ProgramNode", "{statements}\n"),
    new Map("AssignmentNode", "let {variable} = {value};"),
    new Map("BinaryOpNode", "({left} {operator} {right})"),
    new Map("IfNode", "if ({condition}) {block}"),
    new Map("UnknownNode", "// Unhandled node: {type}")
};

Input AST

{
    "type": "ProgramNode",
    "fields": {
        "statements": [
            {
                "type": "AssignmentNode",
                "fields": {
                    "variable": "x",
                    "value": 10
                }
            },
            {
                "type": "IfNode",
                "fields": {
                    "condition": "x > 0",
                    "block": "return x;"
                }
            }
        ]
    }
}

Output

let x = 10;
if (x > 0) return x;

Best Practices

Always Include Fallback Rules

Define a fallback to identify unmapped nodes:

new Map("UnknownNode", "// TODO: Handle {type}");

Keep Templates Modular

Avoid overly complex templates. Break larger mappings into smaller, reusable pieces.

Validate and Test Incrementally

Write maps iteratively and test outputs with diverse inputs.

Use Descriptive Field Names

Make placeholders intuitive for better maintainability:

new Map("AssignmentNode", "let {variable} = {value};");

Next Steps

Now that you understand mapping:

  • Rules: Revisit how parsing creates ASTs.
  • Tokens: Revisit token definitions.
  • Examples: Explore mapping in real-world scenarios.
  • Debugging: Diagnose and resolve mapping issues efficiently.

Clone this wiki locally