Position
This package can be installed separately with
composer require phplrt/position
An offset is what a parser works with: a number of bytes from the beginning of the source. A line and a column is what a person works with. This package converts between the two.
1use Phplrt\Position\PositionFactory; 2use Phplrt\Source\StringSource; 3 4$source = StringSource::createFromString("first line\nsecond line\nthird line"); 5 6$position = new PositionFactory() 7 ->createFromOffset($source, 18); 8 9echo $position; // "2:8" 10echo $position->line; // 2 11echo $position->column; // 8
Both Directions
createFromOffset() turns a number of bytes into a place a person can find,
and createOffsetFromPosition() turns that place back into a number of bytes.
1use Phplrt\Position\Position; 2use Phplrt\Position\PositionFactory; 3 4$factory = new PositionFactory(); 5 6$factory->createFromOffset($source, 18); // 2:8 7$factory->createOffsetFromPosition($source, new Position(2, 8)); // 18
Both are counted from one, not from zero: the very beginning of any source
is 1:1, and the column is counted within its own line rather than from the
beginning of the source.
Neither of them can be pointed out of bounds. An offset past the end of the source gives the very end of it, a column past the end of its line gives the end of that line, and a line past the last one gives the end of the source.
1$factory->createFromOffset($source, 999); // 3:11 2$factory->createOffsetFromPosition($source, new Position(2, 999)); // 22 3$factory->createOffsetFromPosition($source, new Position(99, 1)); // 33
The Position Itself
Position is an immutable pair of numbers that prints itself the way a
compiler does:
1use Phplrt\Position\Position; 2 3echo new Position(7, 12); // "7:12" 4echo new Position(); // "1:1"
A number below one is not a place in a source, so it is refused rather than corrected:
new Position(0); // InvalidArgumentException
What It Does To The Source
Finding a line means counting the delimiters before it, and that means reading the source from its beginning. A source is readable in any order and any number of times, so nothing about it changes:
1echo $source->read(0, 5); // '2 + 2' 2 3$factory->createFromOffset($source, 20); 4 5echo $source->read(0, 5); // '2 + 2' again
A pipe and a socket are read this way as well: everything such a source has given away is kept, so counting the lines of one costs the memory of the data that has been counted.
Reading In Chunks
The source is read in chunks rather than all at once, so a grammar of any size costs the same memory. The size of a chunk is 64 KiB by default:
1use Phplrt\Position\PositionFactory; 2 3$factory = new PositionFactory(chunkSize: 8192); 4 5echo PositionFactory::DEFAULT_CHUNK_SIZE; // 65536
Nothing beyond the offset being looked for is ever read, so a position near the beginning of a large file costs one chunk.
Errors
Everything this package throws implements
Phplrt\Contracts\Source\Exception\SourceExceptionInterface, so it is caught
along with the failures of the source it reads.
Bring Your Own
The two contracts live in packages of their own, so code that only calculates positions does not depend on this implementation:
1use Phplrt\Contracts\Position\PositionFactoryInterface; 2use Phplrt\Contracts\Source\FileInterface; 3 4function report(PositionFactoryInterface $factory, FileInterface $source, int $offset): string 5{ 6 $position = $factory->createFromOffset($source, $offset); 7 8 return \sprintf('%s:%d:%d', $source->pathname, $position->line, $position->column); 9}
PositionInterface guarantees the one-based counting described above, and
PositionInterface::MIN_LINE and PositionInterface::MIN_COLUMN are what
"the beginning" is spelled as.
Both interfaces live in packages of their own, described in Contracts.