jms/parser-lib
jms/parser-lib is a lightweight PHP parser library by JMS, providing reusable parsing components to build custom parsers quickly. Ideal for interpreting structured text and creating DSLs. Full docs available at jmsyst.com/libs/parser-lib.
Installation
composer require jms/parser-lib
Add to composer.json if using a monorepo or custom package structure.
First Use Case: Parsing a Simple Grammar
Define a grammar class extending JMS\Parser\Parser:
use JMS\Parser\Parser;
class MyGrammarParser extends Parser
{
protected function parse()
{
$this->sequence(
$this->match('start'),
$this->match('value'),
$this->match('end')
);
}
}
Parse Input
$parser = new MyGrammarParser();
$result = $parser->parse('start value end');
Key Files to Reference
src/JMS/Parser/Parser.php (Core class)src/JMS/Parser/Exception/ (Error handling)tests/ (Usage examples)Recursive Descent Parsing
Use sequence(), choice(), and repeat() to define grammar rules:
$this->sequence(
$this->match('('),
$this->repeat($this->choice(
$this->match('number'),
$this->match('string')
)),
$this->match(')')
);
Custom Matchers
Extend JMS\Parser\Matcher\Matcher for domain-specific rules:
class EmailMatcher extends Matcher
{
public function match($input)
{
return preg_match('/^[^\s@]+@[^\s@]+\.[^\s@]+$/', $input);
}
}
Error Handling
Catch JMS\Parser\Exception\ParseException for invalid input:
try {
$parser->parse($input);
} catch (ParseException $e) {
// Log or handle error (e.g., $e->getPosition())
}
Integration with Laravel
FormRequest or Validator extensions.
$parser = new MyGrammarParser();
if (!$parser->parse($request->input('data'))) {
return back()->withErrors(['data' => 'Invalid format']);
}
$parser = new ConfigParser();
$config = $parser->parse($input);
Performance Optimization
$parser = new MyGrammarParser();
$parser->compile(); // Cache the parser state
State Management
$parser->reset().Backtracking Overhead
choice() rules can cause exponential backtracking. Optimize with:
$this->choice(
$this->sequence($this->match('A'), $this->match('B')),
$this->sequence($this->match('X'), $this->match('Y'))
);
Input Consumption
match() consumes input. Use $this->peek() for lookahead without consumption:
if ($this->peek()->match('(')) {
// Handle nested structure
}
Whitespace Handling
$parser->setWhitespaceHandling(false);
Enable Verbose Output
$parser->setVerbose(true); // Logs parsing steps to stderr
Inspect Parser State
$parser->getPosition(); // Current input position
$parser->getRemainingInput(); // Unparsed input
Unit Testing
$matcher = $this->createMock(Matcher::class);
$matcher->method('match')->willReturn(true);
$parser->addMatcher('test', $matcher);
Custom Lexers
Extend JMS\Parser\Lexer\Lexer to tokenize input before parsing:
class MyLexer extends Lexer
{
protected function tokenize($input)
{
return ['TOKEN1', 'TOKEN2']; // Custom logic
}
}
AST Builders Attach callbacks to build abstract syntax trees (AST):
$this->match('number')->then(function ($match) {
return new NumberNode($match->getValue());
});
Integration with PHPStan Add type hints to matchers for static analysis:
class JsonMatcher extends Matcher
{
public function match(string $input): bool { ... }
}
strtolower() for case-insensitive rules.mb_* functions if needed:
$this->match(mb_strtolower($input, 'UTF-8'));
How can I help you explore Laravel packages today?