Doctrine Lexer
repository·3.0.x·Indexed 11 days ago
https://github.com/doctrine/lexerA low-level PHP library providing lexical analysis capabilities designed for Top-Down, Recursive Descent Parsers. It serves as a foundational component for Doctrine Annotations and Doctrine ORM (DQL), allowing developers to implement custom lexers by extending AbstractLexer to tokenize input strings into Abstract Syntax Trees (AST).
What's inside Doctrine Lexer
- Doctrine Lexer is a base library providing a lexer designed for use in Top-Down, Recursive Descent Parsers. It is a foundational component used by other major Doctrine projects, specifically Doctrine Annotations and Doctrine ORM (DQL).
What is an AST?
3.0.xAST stands for Abstract Syntax Tree. It is a tree representation of the abstract syntactic structure of source code. Each node in the tree denotes a specific construct occurring in the source code (such as a statement, an expression, or an operator). In compilers and query engines, the AST is used as an intermediate representation to perform analysis or to transform the source code into another format (e.g., transforming DQL into SQL).How a Lexer and Parser work together to build an AST
3.0.xA Lexer and a Parser are used in sequence to process source code (like DQL) into an Abstract Syntax Tree (AST):
- Lexer: Scans the input string and breaks it into a stream of tokens. The parser interacts with the lexer using methods like
moveNext()and by inspecting thelookaheadproperty (which contains the current token's type and value). - Parser: Consumes the tokens provided by the lexer. It uses the token types (e.g.,
Lexer::T_SELECT) to decide which grammar rules to apply (e.g., callingSelectStatement()). - AST (Abstract Syntax Tree): The final output of the parser. It is a tree representation of the syntactic structure of the source code, where each node represents a construct (like a
SelectStatement). This tree can then be used for further transformations, such as converting the language into SQL.
// 1. The Lexer breaks the string into tokens $lexer = new MyLexer('SELECT u FROM User u'); // 2. The Parser uses the Lexer to build the tree $parser = new MyParser($lexer); $AST = $parser->getAST(); // Returns an AST object- Lexer: Scans the input string and breaks it into a stream of tokens. The parser interacts with the lexer using methods like
How to implement a custom lexer
3.0.xTo create a custom lexer, extend
Doctrine\Common\Lexer\AbstractLexerand implement three required abstract methods. These methods define how the lexer identifies patterns and determines token types.getCatchablePatterns(): Returns an array of regex patterns that represent tokens where the value should be captured.getNonCatchablePatterns(): Returns an array of regex patterns for tokens where the value is not captured.getType(string &$value): Determines the integer token type. This method receives the token value by reference, allowing you to modify or filter the value during the type determination process.
/** * Lexical catchable patterns. * * @return string[] */ abstract protected function getCatchablePatterns(); /** * Lexical non-catchable patterns. * * @return string[] */ abstract protected function getNonCatchablePatterns(); /** Retrieve token type. Also processes the token value if necessary. */ abstract protected function getType(string &$value): int;Iterate through tokens using a Lexer
3.0.xOnce a lexer is implemented, you can use it to tokenize a string and iterate through the resulting tokens. The typical workflow is:
- Call
setInput($string)to provide the source text. - Call
moveNext()to advance the lexer to the first token. - Use a loop to check the
lookaheadproperty. Iflookaheadis false, you have reached the end of the input. - Inside the loop, call
moveNext()to advance to the next token. - Access the current token via the
tokenproperty. You can check the token type usingisA(TOKEN_TYPE)and retrieve its content viavalue.
Note: The first
moveNext()call is required to position the lexer at the start of the token stream.$lexer->setInput($string); $lexer->moveNext(); // Advance to first token while (true) { if (!$lexer->lookahead) { break; } $lexer->moveNext(); if ($lexer->token->isA(CharacterTypeLexer::T_UPPER)) { $upperCaseChars[] = $lexer->token->value; } }- Call
Upgrade to Doctrine Lexer 2.0.0
3.0.xWhen upgrading to version 2.0.0, be aware of changes to how tokens are handled:
AbstractLexer::glimpse()andAbstractLexer::peek()returnTokeninstances: These methods now return instances ofDoctrine\Common\Lexer\Token. While this class is array-like, using it as an array is deprecated.- Deprecated Array Access: You should use the properties of the
Tokenclass instead of array syntax. - Deprecated
count()usage: Usingcount()on aTokeninstance is deprecated and has no direct replacement.
Upgrade to Doctrine Lexer 3.0.0
3.0.xWhen upgrading to version 3.0.0, note the following breaking changes:
Doctrine\Common\Lexer\Tokenno longer implementsArrayAccess: You can no longer access token properties using array syntax (e.g.,$token[0]). You must use the class properties instead.- Added Type Declarations: Parameter type declarations have been added to
Doctrine\Common\Lexer\AbstractLexerandDoctrine\Common\Lexer\Token.
Action required: To maintain compatibility and ensure type safety, you should add both parameter type declarations and return type declarations to your custom lexer implementations, following the guidance provided in the
@returnPHPDoc of the methods you are overriding.Implement a custom Lexer by extending AbstractLexer
3.0.xTo create a custom lexer, extend
Doctrine\Common\Lexer\AbstractLexerand implement the following protected methods:getCatchablePatterns(): Return an array of regular expressions for patterns that represent meaningful tokens (e.g., identifiers, numbers, strings).getNonCatchablePatterns(): Return an array of regular expressions for patterns that should be ignored (e.g., whitespace) or used to catch single characters.getType(&$value): Implement the logic to map a captured string$valueto a specific token type integer.
It is a common pattern to categorize token constants into ranges:
- Tokens
< 100: Basic symbols and literals (e.g.,T_INTEGER,T_COMMA). - Tokens
>= 100: Identifiers and names (e.g.,T_IDENTIFIER,T_ALIASED_NAME). - Tokens
>= 200: Keywords (e.g.,T_SELECT,T_WHERE).
use Doctrine\Common\Lexer\AbstractLexer; class MyLexer extends AbstractLexer { public const T_MY_TOKEN = 200; protected function getCatchablePatterns(): array { return ['[a-z]+']; } protected function getNonCatchablePatterns(): array { return ['\s+']; } protected function getType(&$value): int { if ($value === 'my_token') { return self::T_MY_TOKEN; } return 1; // T_NONE } }Use the Doctrine Lexer low-level API
3.0.xThe Lexer provides a low-level API to traverse an input string and analyze tokens by type, value, and position. Use the following methods to control the lexing process:
Input and Position Management
setInput($input): Sets the input data to be tokenized. This immediately resets the lexer and tokenizes the new input.reset(): Resets the lexer state.resetPeek(): Resets the peek pointer to 0.resetPosition($position = 0): Resets the lexer position on the input to the specified position.
Token Lookahead and Validation
isNextToken($token): Checks if the current lookahead matches the specified token.isNextTokenAny(array $tokens): Checks if any of the provided tokens match the current lookahead.isA($value, $token): Checks if the given value is identical to the specified token.peek(): Moves the lookahead token forward.glimpse(): Peeks at the next token, returns it, and immediately resets the peek pointer.
Traversal
moveNext(): Moves to the next token in the input string.skipUntil($type): Skips input tokens until a token of the specified type is encountered.