This document describes debugging techniques and workflows for the PPJ compiler. Effective debugging is essential for understanding compiler behavior and fixing issues.
- Reproduce the Issue: Create minimal test case
- Identify the Phase: Determine which compiler phase fails
- Examine Intermediate Outputs: Check phase-specific output files
- Trace Execution: Follow data flow through compiler phases
- Fix and Verify: Make fix and verify with test case
Output File: compiler-bin/leksicke_jedinke.txt
Contents:
- Token table (symbol table)
- Token stream with line numbers
Debugging Steps:
-
Check Token Recognition:
./run.sh lexer program.c cat compiler-bin/leksicke_jedinke.txt
-
Verify Token Patterns:
- Check if expected tokens are recognized
- Verify token line numbers
- Check for unrecognized characters
-
Common Issues:
- Token not recognized: Check lexer rule pattern
- Wrong token: Check rule ordering (maximal munch)
- Missing token: Check state transitions
Example Debug Output:
tablica znakova:
indeks uniformni znak izvorni tekst
0 KR_INT int
1 IDN main
2 L_ZAGRADA (
Output Files:
compiler-bin/generativno_stablo.txt(generative tree)compiler-bin/sintaksno_stablo.txt(syntax tree)
Debugging Steps:
-
Check Parse Tree Structure:
./run.sh syntax program.c cat compiler-bin/sintaksno_stablo.txt
-
Verify Grammar Productions:
- Check if expected productions are applied
- Verify tree structure matches grammar
- Check for parse errors
-
Common Issues:
- Parse error: Check grammar productions
- Wrong tree structure: Check production alternatives
- Missing nodes: Check grammar completeness
Example Debug Output:
0:<prijevodna_jedinica>
1:<vanjska_deklaracija>
2:<definicija_funkcije>
3:KR_INT
4:IDN main
5:L_ZAGRADA
...
Output Files:
compiler-bin/tablica_simbola.txt(symbol table)compiler-bin/semanticko_stablo.txt(annotated AST)
Debugging Steps:
-
Check Symbol Table:
./run.sh semantic program.c cat compiler-bin/tablica_simbola.txt
-
Verify Type Information:
- Check variable types
- Verify function signatures
- Check scope hierarchy
-
Common Issues:
- Undefined identifier: Check symbol table construction
- Type mismatch: Check type checking rules
- Scope error: Check scope management
Example Debug Output:
Global Scope:
main : int(void) [defined=true]
Function Scope - main:
x : int [const=false]
Output File: compiler-bin/a.frisc
Debugging Steps:
-
Check Generated Assembly:
./run.sh program.c cat compiler-bin/a.frisc
-
Verify Code Structure:
- Check function prologues/epilogues
- Verify stack management
- Check register usage
-
Common Issues:
- Wrong instructions: Check code generation rules
- Stack imbalance: Check stack management
- Wrong values: Check expression evaluation
Example Debug Output:
F_MAIN:
SUB R7, 4, R7 ; Allocate local variable
MOVE 42, R0
STORE R0, R7+0 ; Store local variable
ADD R7, 4, R7 ; Deallocate
MOVE R0, R6 ; Return value
RETConsole Interface:
node node_modules/friscjs/consoleapp/frisc-console.js compiler-bin/a.friscWeb Interface:
open node_modules/friscjs/webapp/index.html
# Load compiler-bin/a.frisc in web interfaceDebugging Features:
- Step-by-step execution
- Register inspection
- Memory inspection
- Breakpoints (web interface)
Wrong Return Value:
- Check expression evaluation
- Verify return value placement in R6
- Check function epilogue
Stack Overflow:
- Check stack pointer initialization
- Verify stack frame size
- Check for stack imbalance
Infinite Loop:
- Check loop condition evaluation
- Verify loop increment/decrement
- Check control flow instructions
Enable Debug Logging:
Logger.getLogger("hr.fer.ppj").setLevel(Level.FINE);Log Output:
- Phase transitions
- Error messages
- Intermediate states
Add Debug Output:
System.out.println("Debug: " + debugInfo);Remove Before Commit: Always remove debug print statements
Write Focused Tests:
@Test
void testSpecificFeature() {
// Test minimal case
// Verify expected behavior
}Isolate Issues: Create minimal test cases to isolate problems
Symptoms: Lexer doesn't recognize expected token
Debugging:
- Check lexer rule pattern
- Verify macro expansion
- Check state transitions
- Verify rule ordering
Fix: Update lexer rule or add new rule
Symptoms: Parser reports syntax error
Debugging:
- Check token stream (lexer output)
- Verify grammar productions
- Check for grammar conflicts
- Review error recovery
Fix: Update grammar or fix source code
Symptoms: Semantic analyzer reports type mismatch
Debugging:
- Check symbol table (variable types)
- Verify type checking rules
- Check implicit conversions
- Review type synthesis
Fix: Update type checking rules or fix source code
Symptoms: Generated assembly doesn't match expected
Debugging:
- Check AST structure
- Verify code generation rules
- Check register allocation
- Review stack management
Fix: Update code generation rules
Create smallest possible test case that reproduces issue:
// Minimal test case
int main(void) {
return 0; // Add minimal code to reproduce issue
}Test changes incrementally:
- Make small changes
- Test after each change
- Verify intermediate outputs
Keep notes during debugging:
- What was tested
- What was found
- What was fixed
Commit working state before debugging:
git commit -m "Working state before debugging"Always verify fixes:
- Test with original failing case
- Test with related cases
- Run full test suite
- Test Strategy: Testing methodology
- Example Programs: Test program catalog
- FRISC Simulator Guide: Simulator usage
Effective debugging requires systematic approach, understanding of compiler phases, and use of appropriate debugging tools. Following these practices leads to efficient issue resolution.