Skip to content

Debugging

Tools and techniques for debugging Nic programs and the compiler itself.

Inspection Flags

View intermediate compilation stages to understand how your code is processed.

--ast (Abstract Syntax Tree)

View the parsed AST before type checking:

bash
nicc --ast file.nic
nicc build --ast
nicc run --ast file.nic

Output shows the raw parse tree with source locations:

Program
├── FnDecl "main" @ 1:1-5:2
│   ├── Params: []
│   ├── ReturnType: unit
│   └── Body: Block
│       └── ReturnStmt @ 4:5-4:12

--sast (Typed AST)

View the type-checked SAST with inferred types:

bash
nicc --sast file.nic       # Default verbosity
nicc --sast -v file.nic    # Balanced (some type annotations)
nicc --sast -vv file.nic   # Verbose (all type annotations)

Verbosity levels:

LevelDescription
DefaultClean output, minimal annotations
-vBalanced, shows inferred types on variables
-vvVerbose, shows all type annotations

Example with -vv:

SProgram
└── SFnDecl "main" : () -> unit
    └── SBlock : unit
        └── SReturn (SLit (SIntLit 0 : i32)) : unit

--llvm (LLVM IR)

View the generated LLVM IR:

bash
nicc --llvm file.nic
nicc build --llvm

Useful for understanding code generation and optimization:

llvm
define i32 @main() {
entry:
  ret i32 0
}

Parser Tracing

Debug parser issues with detailed trace output.

Enable Tracing

bash
nicc --debug --ast file.nic            # Trace to stderr
nicc --debug --trace-log trace.txt --ast file.nic  # Trace to file

Trace Output Format

[ENTER] topDeclP @ 1:1 | "fn main() -> unit { ... }"
  [ENTER] fdeclP @ 1:1 | "fn main() -> unit { ... }"
    [ENTER] formalsP @ 1:8 | "() -> unit { ... }"
    [OK]    formalsP @ 1:10
    [ENTER] typeP @ 1:14 | "unit { ... }"
    [OK]    typeP @ 1:18
  [OK]    fdeclP @ 1:31
[OK]    topDeclP @ 1:31

Legend:

  • [ENTER] - Entering a parser function
  • [OK] - Parser succeeded
  • [FAIL] - Parser failed (backtracks)
  • @ line:col - Source position
  • | "..." - Input preview at that position

When to Use Parser Tracing

  • Unexpected parse errors
  • Understanding operator precedence
  • Debugging custom syntax
  • Finding where parsing diverges from expectation

Note: Parser tracing generates large output. For complex files, always use --trace-log to write to a file.

Error Formats

Pretty Errors (default)

Human-readable errors with source context:

bash
nicc file.nic --error-format pretty
error[E0001]: Type mismatch
  --> src/main.nic:5:12
   |
 5 |     let x: i32 = "hello";
   |            ^^^   ^^^^^^^ expected i32, found string
   |

JSON Errors

Machine-readable JSON for tool integration:

bash
nicc file.nic --error-format json
json
{
  "errors": [
    {
      "type": "TypeError",
      "message": "Type mismatch: expected i32, found string",
      "span": {
        "file": "src/main.nic",
        "start": {"line": 5, "column": 12},
        "end": {"line": 5, "column": 19}
      }
    }
  ]
}

Useful for:

  • IDE integration
  • CI/CD pipelines
  • Custom error processing tools

LSP Debug Logging

Debug Language Server Protocol issues:

bash
NICC_LSP_DEBUG=1 nicc lsp

Or specify a custom log file:

bash
nicc lsp --log /tmp/nicc-lsp.log

Logs include:

  • Request/response pairs
  • File change notifications
  • Module graph updates
  • Cache hits/misses
  • Compilation timing

Default log location: ~/.nicc/logs/lsp.log

Common Issues

Parse Errors

Symptom: Unexpected token error

Debug steps:

  1. Use --debug --ast to see where parsing fails
  2. Check trace for the exact position
  3. Look for missing semicolons, unbalanced braces
bash
nicc --debug --trace-log trace.txt --ast broken.nic
# Search trace.txt for [FAIL] patterns

Type Errors

Symptom: Type mismatch or inference failure

Debug steps:

  1. Use --sast -vv to see all inferred types
  2. Check that types match at call sites
  3. Verify generic instantiation
bash
nicc --sast -vv file.nic 2>&1 | less

Module Resolution

Symptom: Module not found

Debug steps:

  1. Check NIC_STD_PATH is set correctly
  2. Verify file exists at expected path
  3. Check import statement matches file location
bash
# Verify std library location
echo $NIC_STD_PATH
ls ~/.nicc/std/

# Check import path resolution
# import std.io -> ~/.nicc/std/io.nic (or std/io/mod.nic)

Codegen Errors

Symptom: LLVM error or crash during compilation

Debug steps:

  1. Use --llvm to inspect generated IR
  2. Check for unsupported patterns
  3. Try simpler test case
bash
nicc --llvm file.nic > ir.ll
# Examine ir.ll for issues

Runtime Debugging

Printf Debugging

The simplest debugging technique:

nic
fn process(x: i32) -> i32 {
    printf("process called with x=%d\n", x);
    let result = x * 2;
    printf("result=%d\n", result);
    return result;
}

Assert Statements

Use assertions for invariant checking (if implemented):

nic
fn divide(a: i32, b: i32) -> i32 {
    // Runtime check
    if b == 0 {
        printf("Error: division by zero\n");
        return 0;
    }
    return a / b;
}

Performance Profiling

Build Timing

Measure compilation time:

bash
time nicc build --release

JIT vs Native

Compare execution modes:

bash
# JIT (default, faster compilation)
time nicc run

# Native (faster execution)
time nicc run --build --release

Inspection Overhead

Note: --ast, --sast, and --llvm flags add overhead. Don't use them when benchmarking.

Tips

  1. Start simple: Reduce the problem to a minimal test case
  2. Use verbosity: -vv shows all type information
  3. Log to file: Parser traces are huge, always use --trace-log
  4. Check error format: JSON errors are great for automated parsing
  5. LSP logs: Enable NICC_LSP_DEBUG=1 for IDE issues

See Also

Released under the MIT License.