Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions docs/reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,13 @@ jperl [options] [program | -e 'command'] [arguments]

## Debugging Options

- **`--debug`** - Show debug information
- **`-d`** - Run script under the interactive Perl debugger
```bash
./jperl -d script.pl
```
See [Debugger Reference](debugger.md) for commands and usage.

- **`--debug`** - Show debug information (compiler internals)
```bash
./jperl --debug -E 'say "test"'
```
Expand Down Expand Up @@ -233,7 +239,8 @@ The following standard Perl options are not yet implemented:
- **`-t`** - Taint checks with warnings
- **`-u`** - Dumps core after compiling
- **`-U`** - Allows unsafe operations
- **`-d[t][:debugger]`** - Run under debugger
- **`-dt`** - Run under threaded debugger
- **`-d:debugger`** - Run under custom debugger module
- **`-D[number/list]`** - Set debugging flags
- **`-C [number/list]`** - Control Unicode features

Expand Down
151 changes: 151 additions & 0 deletions docs/reference/debugger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Perl Debugger Reference

The PerlOnJava debugger provides an interactive debugging environment similar to Perl's built-in debugger.

## Starting the Debugger

```bash
./jperl -d script.pl [arguments]
```

The debugger stops at the first executable statement and displays:

```
main::(script.pl:5):
5: my $x = 10;
DB<1>
```

## Debugger Commands

### Execution Control

| Command | Description |
|---------|-------------|
| `s` | **Step into** - Execute one statement, stepping into subroutine calls |
| `n` | **Next** - Execute one statement, stepping over subroutine calls |
| `r` | **Return** - Execute until current subroutine returns |
| `c [line]` | **Continue** - Run until breakpoint, end, or specified line (one-time) |
| `q` | **Quit** - Exit the debugger and program |

Press **Enter** to repeat the last command (default: `n`).

### Breakpoints

| Command | Description |
|---------|-------------|
| `b [line]` | Set breakpoint at current or specified line |
| `b file:line` | Set breakpoint at line in specified file |
| `B [line]` | Delete breakpoint at current or specified line |
| `B *` | Delete all breakpoints |
| `L` | List all breakpoints |

### Source Display

| Command | Description |
|---------|-------------|
| `l [range]` | List source code (e.g., `l 10-20`, `l 15`) |
| `.` | Show current line |
| `T` | Show stack trace (call stack) |

### Expression Evaluation

| Command | Description |
|---------|-------------|
| `p expr` | Print expression result |
| `x expr` | Dump expression with Data::Dumper formatting |

### Help

| Command | Description |
|---------|-------------|
| `h` or `?` | Show help |

## Debug Variables

The debugger provides access to standard Perl debug variables:

| Variable | Description |
|----------|-------------|
| `$DB::single` | Single-step mode (1 = enabled) |
| `$DB::trace` | Trace mode (1 = enabled) |
| `$DB::signal` | Signal flag |
| `$DB::filename` | Current filename |
| `$DB::line` | Current line number |
| `%DB::sub` | Subroutine locations (`subname => "file:start-end"`) |
| `@DB::args` | Arguments of current subroutine |

## Examples

### Basic Stepping

```
$ ./jperl -d script.pl
main::(script.pl:5):
5: my $x = 10;
DB<1> n
main::(script.pl:6):
6: my $y = foo($x);
DB<2> s
main::(script.pl:2):
2: my ($arg) = @_;
DB<3>
```

### Setting Breakpoints

```
DB<1> b 15
Breakpoint set at script.pl:15
DB<1> b other.pl:20
Breakpoint set at other.pl:20
DB<1> L
Breakpoints:
script.pl:15
other.pl:20
DB<1> c
```

### Evaluating Expressions

```
DB<1> p $x + $y
42
DB<1> p "@DB::args"
10 20 hello
DB<1> x \@array
$VAR1 = [
1,
2,
3
];
```

### Inspecting Subroutine Locations

```
DB<1> p $DB::sub{"main::foo"}
script.pl:10-15
DB<1> x \%DB::sub
$VAR1 = {
'main::foo' => 'script.pl:10-15',
'main::bar' => 'script.pl:20-25'
};
```

## Limitations

The following Perl debugger features are not yet implemented:

- Watchpoints (`w` command)
- Actions (`a` command)
- Conditional breakpoints (`b line condition`)
- Custom debugger modules (`-d:Module`)
- Restart (`R` command)
- History and command editing
- Lexical variable inspection (expressions evaluate in package scope)

## See Also

- [CLI Options](cli-options.md) - All command-line options
- [perldebug](https://perldoc.perl.org/perldebug) - Perl's debugger documentation
66 changes: 59 additions & 7 deletions docs/reference/feature-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,68 @@ PerlOnJava implements most core Perl features with some key differences:
- ✅ **Perl-like runtime error messages**: Runtime errors are formatted similarly to Perl's.
- ✅ **Comments**: Support for comments and POD (documentation) in code is implemented.
- ✅ **Environment**: Support for `PERL5LIB`, `PERL5OPT` environment variables.
- 🚧 **Perl debugger**: The built-in Perl debugger (`perl -d`) is work in progress.
- ✅ Basic commands: `n` (next), `s` (step), `c` (continue), `q` (quit), `l` (list), `b` (breakpoint), `B` (delete breakpoint), `L` (list breakpoints), `h` (help)
- ✅ Debug variables: `$DB::single`, `$DB::trace`, `$DB::signal`, `$DB::filename`, `$DB::line`
- ❌ `-d:MOD` for Devel modules (e.g., `-d:NYTProf`)
- ❌ `perl5db.pl` compatibility
- ❌ Expression evaluation commands (`p`, `x`)
- 🚧 **Perl-like warnings**: Warnings is work in progress. Some warnings need to be formatted to resemble Perl's output.

---

## Perl Debugger

The built-in Perl debugger (`perl -d`) provides interactive debugging. See [Debugger Reference](debugger.md) for full documentation.

### Execution Commands
| Command | Status | Description |
|---------|--------|-------------|
| `s` | ✅ | Step into - execute one statement, entering subroutines |
| `n` | ✅ | Next - execute one statement, stepping over subroutines |
| `r` | ✅ | Return - execute until current subroutine returns |
| `c [line]` | ✅ | Continue - run until breakpoint or specified line |
| `q` | ✅ | Quit - exit the debugger |

### Breakpoints
| Command | Status | Description |
|---------|--------|-------------|
| `b [line]` | ✅ | Set breakpoint at line |
| `b file:line` | ✅ | Set breakpoint at line in file |
| `B [line]` | ✅ | Delete breakpoint |
| `B *` | ✅ | Delete all breakpoints |
| `L` | ✅ | List all breakpoints |
| `b line condition` | ❌ | Conditional breakpoints |

### Source and Stack
| Command | Status | Description |
|---------|--------|-------------|
| `l [range]` | ✅ | List source code |
| `.` | ✅ | Show current line |
| `T` | ✅ | Stack trace |
| `w expr` | ❌ | Watch expression |
| `a line command` | ❌ | Set action at line |

### Expression Evaluation
| Command | Status | Description |
|---------|--------|-------------|
| `p expr` | ✅ | Print expression result |
| `x expr` | ✅ | Dump expression with Data::Dumper |

### Debug Variables
| Variable | Status | Description |
|----------|--------|-------------|
| `$DB::single` | ✅ | Single-step mode flag |
| `$DB::trace` | ✅ | Trace mode flag |
| `$DB::signal` | ✅ | Signal flag |
| `$DB::filename` | ✅ | Current filename |
| `$DB::line` | ✅ | Current line number |
| `%DB::sub` | ✅ | Subroutine locations (name → file:start-end) |
| `@DB::args` | ✅ | Current subroutine arguments |

### Not Implemented
- ❌ `-d:Module` - Custom debugger modules (e.g., `-d:NYTProf`)
- ❌ `perl5db.pl` compatibility
- ❌ `R` - Restart program
- ❌ History and command editing

---

### Command line switches
## Command Line Switches

- ✅ Accept input program in several ways:
1. **Piped input**: `echo 'print "Hello\n"' | ./jperl` - reads from pipe and executes immediately
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,9 @@ public void visit(BlockNode node) {
if (DebugState.debugMode && stmtTokenIndex >= 0) {
boolean skipDebug = (stmt instanceof AbstractNode an && an.getBooleanAnnotation("skipDebug"));
if (!skipDebug) {
int lineNumber = errorUtil.getLineNumber(stmtTokenIndex);
// Use getLineNumberAccurate() because subroutine bodies may be compiled
// lazily after the main script, making cached line numbers unreliable
int lineNumber = errorUtil.getLineNumberAccurate(stmtTokenIndex);
int fileIdx = addToStringPool(sourceName);
emit(Opcodes.DEBUG);
emit(fileIdx);
Expand Down Expand Up @@ -4159,6 +4161,13 @@ private void visitNamedSubroutine(SubroutineNode node) {
emit(nameIdx);
emitReg(codeReg);

// Step 7: Register subroutine location for %DB::sub (only in debug mode)
if (DebugState.debugMode && errorUtil != null) {
int startLine = errorUtil.getLineNumber(node.getIndex());
// Use start line as end line for now (accurate end would require tracking block end)
DebugState.registerSubroutine(fullName, sourceName, startLine, startLine);
}

lastResultReg = -1;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1541,7 +1541,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
int fileIdx = bytecode[pc++];
int line = bytecode[pc++];
String file = code.stringPool[fileIdx];
DebugHooks.debug(file, line);
DebugHooks.debug(file, line, code, registers);
}

default -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,14 @@ public static String disassemble(InterpretedCode interpretedCode) {
break;
}

case Opcodes.DEBUG: {
int fileIdx = interpretedCode.bytecode[pc++];
int line = interpretedCode.bytecode[pc++];
sb.append("DEBUG file=\"").append(interpretedCode.stringPool[fileIdx])
.append("\" line=").append(line).append("\n");
break;
}

default:
sb.append("UNKNOWN(").append(opcode).append(")\n");
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ public static RuntimeScalar evalString(String perlCode,
// first evalString overload above: it corrupts die/warn location baking.
BytecodeCompiler compiler = new BytecodeCompiler(
sourceName + " (eval)",
sourceLine
sourceLine,
errorUtil
);
InterpretedCode evalCode = compiler.compile(ast, ctx); // Pass ctx for context propagation
if (RuntimeCode.DISASSEMBLE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.perlonjava.frontend.lexer.LexerTokenType;
import org.perlonjava.frontend.semantic.ScopedSymbolTable;
import org.perlonjava.frontend.semantic.SymbolTable;
import org.perlonjava.runtime.debugger.DebugState;
import org.perlonjava.runtime.mro.InheritanceResolver;
import org.perlonjava.runtime.runtimetypes.*;

Expand Down Expand Up @@ -659,6 +660,14 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
codeRef.value = new RuntimeCode(subName, attributes);
}

// Register subroutine location for %DB::sub (only in debug mode)
if (DebugState.debugMode && parser.ctx.errorUtil != null && block != null) {
int startLine = parser.ctx.errorUtil.getLineNumber(block.tokenIndex);
// Use current position as end for now (could track block end for accuracy)
int endLine = parser.ctx.errorUtil.getLineNumber(parser.tokenIndex);
DebugState.registerSubroutine(fullName, parser.ctx.compilerOptions.fileName, startLine, endLine);
}

// Initialize placeholder metadata (accessed via codeRef.value)
RuntimeCode placeholder = (RuntimeCode) codeRef.value;
placeholder.prototype = prototype;
Expand Down
Loading