phplrt 4.0

Reading a Snippet

This page is about the source code lines behind the diagnostics. For printing them, see Error Reporting.

SnippetReader is what a renderer reads the source code with. It takes the analysis of an error and reads the lines of the source code around the fragment it covers, telling which part of which line is at fault.

Use it when you want the lines rather than the picture - your own renderer, a web page highlighting the error, or an editor jumping to it.

1use Phplrt\Exception\Analyzer;
2use Phplrt\Exception\SnippetReader;
3
4$result = new Analyzer()->analyze($e);
5$lines = new SnippetReader()->read($result, lines: 1);

The result is indexed by the line numbers, so the keys are as meaningful as the values:

1foreach ($lines as $number => $line) {
2    \printf("%d @%d %s\n", $number, $line->offset, $line->value);
3}
11 @0 name = "phplrt"
22 @16 version = four
33 @31 debug = true

Reading the source may fail, so both methods declare Phplrt\Contracts\Source\Exception\SourceExceptionInterface.

The Lines

Every line is a SourceLine:

1$line->number; // the line number, starting from 1
2$line->offset; // the byte the line starts at, counted from the beginning of the source
3$line->value;  // the line without its trailing delimiter

A line holding a part of the fragment is a CapturedSourceLine, which adds where exactly that part is:

 1use Phplrt\Exception\Snippet\CapturedSourceLine;
 2
 3foreach ($lines as $line) {
 4    if (!$line instanceof CapturedSourceLine) {
 5        continue;
 6    }
 7
 8    $line->captured->offset; // the byte of the line the fragment starts at
 9    $line->captured->length; // the number of bytes captured on this line
10    $line->captured->endsAt; // $offset + $length
11
12    // The same fragment as an offset inside the whole source
13    $line->offset + $line->captured->offset;
14}

Both are counted in bytes from the beginning of the line and start at zero, so they go straight into substr():

\substr($line->value, $line->captured->offset, $line->captured->length);

Note that a fragment spanning several lines passes through the ones in the middle, and a line it only passes through captures nothing of its own - length is zero. That is how a multi-line fragment is told from one pointing at a single position.

 1foreach ($lines as $line) {
 2    \printf(
 3        "%d %s %s\n",
 4        $line->number,
 5        $line instanceof CapturedSourceLine
 6            ? \sprintf('[%d..%d]', $line->captured->offset, $line->captured->endsAt)
 7            : '       ',
 8        $line->value,
 9    );
10}
11         name = "phplrt"
22 [10..14] version = four
33         debug = true

Lines Around

The second argument is the number of lines read before and after the fragment, two by default:

1use Phplrt\Exception\SnippetReader;
2
3$reader->read($result);            // SnippetReader::DEFAULT_LINES_AROUND
4$reader->read($result, lines: 0);  // only the lines the fragment is on
5$reader->read($result, lines: 10);

Lines that do not exist - before the beginning or after the end of the source

  • are simply not there, so asking for ten around the first line gives fewer than twenty-one.

Any Fragment

An error is not the only thing worth showing. A FailureResult describes any fragment of any source, with no exception involved:

 1use Phplrt\Exception\Analysis\FailureInterval;
 2use Phplrt\Exception\Analysis\FailureResult;
 3use Phplrt\Exception\SnippetReader;
 4use Phplrt\Position\Position;
 5use Phplrt\Source\FileSource;
 6
 7$lines = new SnippetReader()->read(new FailureResult(
 8    class: '',
 9    message: '',
10    source: FileSource::createFromPathname('config.txt'),
11    position: new Position(),
12    interval: new FailureInterval(offset: 26, length: 4),
13), lines: 2);

An error covering no fragment of its own - an ordinary exception, which knows only the line it was thrown on, so its position starts at the first column - captures that whole line. A position pointing at a column of its own tells where exactly the error is, and captures that place alone, as does a fragment of a zero length.

Large Sources

The source is read in chunks rather than all at once, so a snippet out of a file of any size costs the same. The default chunk is 8 KiB:

1use Phplrt\Exception\SnippetReader;
2use Phplrt\Position\PositionFactory;
3
4$reader = new SnippetReader(
5    positions: new PositionFactory(),
6    chunkSize: SnippetReader::DEFAULT_CHUNK_SIZE,
7);

Lines are separated by "\n", and the "\r" of a "\r\n" delimiter belongs to the delimiter rather than to the line. The data left after the last delimiter is the line the source ends with, so a source ending with a delimiter has an empty last line.

The same reader is the one RustStyleRenderer reads with, so a renderer of your own configures it the way it needs:

1use Phplrt\Exception\ErrorPrinter;
2use Phplrt\Exception\Printer\Renderer\RawRustStyleRenderer;
3use Phplrt\Exception\SnippetReader;
4
5$printer = new ErrorPrinter(new RawRustStyleRenderer(
6    new SnippetReader(chunkSize: 65536),
7));