Cursor
A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.
Supported Encodings
As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.
Usage
Instantiating a new Cursor is as simple as:
use League\CommonMark\Parser\Cursor;
$cursor = new Cursor('Hello World!');
Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).
Methods
You can then call any of the following methods to parse the string within that Cursor:
| Method | Purpose |
|---|---|
getPosition() |
Returns the current position/index of the Cursor within the string |
getColumn() |
Returns the current column (used when handling tabbed indentation) |
getIndent() |
Returns the current amount of indentation |
isIndented() |
Returns whether the cursor is indented to INDENT_LEVEL |
getCharacter(int $index) |
Returns the character at the given absolute position |
getCurrentCharacter() |
Returns the character at the current position |
peek() |
Returns the next character without changing the current position of the cursor |
peek(int $offset) |
Returns the character $offset chars away without changing the current position of the cursor |
getNextNonSpacePosition() |
Returns the position of the next character which is not a space or tab |
getNextNonSpaceCharacter() |
Returns the next character which isn’t a space (or tab) |
advance() |
Moves the cursor forward by 1 character |
advanceBy(int $characters) |
Moves the cursor forward by $characters characters |
advanceBy(int $characters, true) |
Moves the cursor forward by $characters characters, handling tabs as columns |
advanceBySpaceOrTab() |
Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise |
advanceToNextNonSpaceOrTab() |
Advances forward past all spaces and tabs found, returning the number of such characters found |
advanceToNextNonSpaceOrNewline() |
Advances forward past all spaces and newlines found, returning the number of such characters found |
advanceToEnd() |
Advances the position to the very end of the string, returning the number of such characters passed |
match(string $regex) |
Attempts to match the given $regex against the remainder; returns null if matching fails, otherwise it advances past and returns the matched text |
matchInPlace(string $regex) |
Like match(), but matches at the cursor’s position within the whole line instead of copying the remainder; see below |
getPreviousText() |
Returns the text that was just advanced through during the last advance__() or match() operation |
getRemainder() |
Returns the contents of the string from the current position through the end of the string |
isBlank() |
Returns whether the remainder is blank (we’re at the end or only space characters remain) |
isAtEnd() |
Returns whether the cursor has reached the end of the string |
saveState() |
Encapsulates the current state of the cursor into an array in case you need to restoreState() later |
restoreState($state) |
Pass the result of saveState() back into here to restore the original state of the Cursor |
getLine() |
Returns the entire string (not taking the position into account) |
Regular Expression Matching
The Cursor offers two ways to match a regular expression at the current position. They differ in what the pattern is matched against:
match()copies the remainder and matches against that copy, so the subject begins at the cursor.^and\Aanchor at the cursor, and constructs which examine what precedes the match position (lookbehinds,\b) see the start of the subject there — never the actual preceding characters.matchInPlace()(available since 2.10) matches against the whole line starting at the cursor’s position, using PCRE’s native offset semantics.\Ganchors at the cursor,^means the start of the line, and lookbehinds and\bsee the characters actually preceding the cursor.
matchInPlace() has two advantages:
- It avoids copying the remainder, so repeated calls (such as a scanning loop) stay fast instead of paying for a copy of everything left in the line on every call.
- Because the text before the cursor stays visible to the pattern, it can answer contextual questions
match()cannot — for example,/(?<!\w)@\w+/only matching a mention when the preceding character isn’t a word character.
One exception: when the cursor has partially consumed a tab, no position within the line can represent it, so matchInPlace() falls back to matching a copy of the remainder with the leftover tab expanded into spaces. \G still anchors at the cursor there, but the text before the cursor is not visible to the pattern in that state.
To migrate a pattern from match() to matchInPlace(), replace its leading ^ (or \A) with \G, and double-check that any \b or lookbehind still means what you want now that it can see the preceding text:
$cursor->match('/^#+/'); // anchors at the cursor
$cursor->matchInPlace('/\G#+/'); // equivalent, without copying the remainder