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:
nicc --ast file.nic
nicc build --ast
nicc run --ast file.nicOutput 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:
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:
| Level | Description |
|---|---|
| Default | Clean output, minimal annotations |
-v | Balanced, shows inferred types on variables |
-vv | Verbose, 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:
nicc --llvm file.nic
nicc build --llvmUseful for understanding code generation and optimization:
define i32 @main() {
entry:
ret i32 0
}Parser Tracing
Debug parser issues with detailed trace output.
Enable Tracing
nicc --debug --ast file.nic # Trace to stderr
nicc --debug --trace-log trace.txt --ast file.nic # Trace to fileTrace 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:31Legend:
[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:
nicc file.nic --error-format prettyerror[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:
nicc file.nic --error-format 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:
NICC_LSP_DEBUG=1 nicc lspOr specify a custom log file:
nicc lsp --log /tmp/nicc-lsp.logLogs 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:
- Use
--debug --astto see where parsing fails - Check trace for the exact position
- Look for missing semicolons, unbalanced braces
nicc --debug --trace-log trace.txt --ast broken.nic
# Search trace.txt for [FAIL] patternsType Errors
Symptom: Type mismatch or inference failure
Debug steps:
- Use
--sast -vvto see all inferred types - Check that types match at call sites
- Verify generic instantiation
nicc --sast -vv file.nic 2>&1 | lessModule Resolution
Symptom: Module not found
Debug steps:
- Check
NIC_STD_PATHis set correctly - Verify file exists at expected path
- Check import statement matches file location
# 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:
- Use
--llvmto inspect generated IR - Check for unsupported patterns
- Try simpler test case
nicc --llvm file.nic > ir.ll
# Examine ir.ll for issuesRuntime Debugging
Printf Debugging
The simplest debugging technique:
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):
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:
time nicc build --releaseJIT vs Native
Compare execution modes:
# JIT (default, faster compilation)
time nicc run
# Native (faster execution)
time nicc run --build --releaseInspection Overhead
Note: --ast, --sast, and --llvm flags add overhead. Don't use them when benchmarking.
Tips
- Start simple: Reduce the problem to a minimal test case
- Use verbosity:
-vvshows all type information - Log to file: Parser traces are huge, always use
--trace-log - Check error format: JSON errors are great for automated parsing
- LSP logs: Enable
NICC_LSP_DEBUG=1for IDE issues
See Also
- CLI Commands - All debug flags
- Editor Support (LSP) - LSP debugging