PHP Conference Nagoya 2025

Parser examples

Example #1 Simple calculator

<?php

use Parle\{Parser, ParserException, Lexer, Token};

$p = new Parser;
$p->token("INTEGER");
$p->left("'+' '-' '*' '/'");

$p->push("start", "exp");
$prod_add = $p->push("exp", "exp '+' exp");
$prod_sub = $p->push("exp", "exp '-' exp");
$prod_mul = $p->push("exp", "exp '*' exp");
$prod_div = $p->push("exp", "exp '/' exp");
$p->push("exp", "INTEGER"); /* Production index unused. */

$p->build();

$lex = new Lexer;
$lex->push("[+]", $p->tokenId("'+'"));
$lex->push("[-]", $p->tokenId("'-'"));
$lex->push("[*]", $p->tokenId("'*'"));
$lex->push("[/]", $p->tokenId("'/'"));
$lex->push("\\d+", $p->tokenId("INTEGER"));
$lex->push("\\s+", Token::SKIP);

$lex->build();

$exp = array(
"1 + 1",
"33 / 10",
"100 * 45",
"17 - 45",
);

foreach (
$exp as $in) {
if (!
$p->validate($in, $lex)) {
throw new
ParserException("Failed to validate input");
}

$p->consume($in, $lex);

while (
Parser::ACTION_ERROR != $p->action && Parser::ACTION_ACCEPT != $p->action) {
switch (
$p->action) {
case
Parser::ACTION_ERROR:
throw new
ParserException("Parser error");
break;
case
Parser::ACTION_SHIFT:
case
Parser::ACTION_GOTO:
case
Parser::ACTION_ACCEPT:
break;
case
Parser::ACTION_REDUCE:
switch (
$p->reduceId) {
case
$prod_add:
$l = $p->sigil(0);
$r = $p->sigil(2);
echo
"$l + $r = " . ($l + $r) . "\n";
break;
case
$prod_sub:
$l = $p->sigil(0);
$r = $p->sigil(2);
echo
"$l - $r = " . ($l - $r) . "\n";
break;
case
$prod_mul:
$l = $p->sigil(0);
$r = $p->sigil(2);
echo
"$l * $r = " . ($l * $r) . "\n";
break;
case
$prod_div:
$l = $p->sigil(0);
$r = $p->sigil(2);
echo
"$l / $r = " . ($l / $r) . "\n";
break;
}
break;
}
$p->advance();
}
}

Example #2 Parse words out from a sentence

<?php

use Parle\{Lexer, Token, Parser, ParserException};

$p = new Parser;
$p->token("WORD");
$p->push("START", "SENTENCE");
$p->push("SENTENCE", "WORDS");
$prod_word_0 = $p->push("WORDS", "WORDS WORD");
$prod_word_1 = $p->push("WORDS", "WORD");
$p->build();

$lex = new Lexer;
$lex->push("[^\s]{-}[\.,\:\;\?]+", $p->tokenId("WORD"));
$lex->push("[\s\.,\:\;\?]+", Token::SKIP);
$lex->build();

$in = "Dis-moi où est ton papa?";
$p->consume($in, $lex);
do {
switch (
$p->action) {
case
Parser::ACTION_ERROR:
throw new
ParserException("Error");
break;
case
Parser::ACTION_SHIFT:
case
Parser::ACTION_GOTO:
/* var_dump($p->trace());*/
break;
case
Parser::ACTION_REDUCE:
$rid = $p->reduceId();
if (
$rid == $prod_word_1) {
var_dump($p->sigil(0));
} if (
$rid == $prod_word_0) {
var_dump($p->sigil(1));
}
break;
}
$p->advance();
} while (
Parser::ACTION_ACCEPT != $p->action);
add a note

User Contributed Notes 2 notes

up
1
Anonymous
1 year ago
For example 1:

$p->left("'+' '-' '*' '/'");

should be

$p->left("'+' '-'");
$p->left("'*' '/'");

in order for operator precedence to be respected.
up
1
andres1gb at gmail dot com
4 years ago
The second example (word count) calls a method reduceId(), instead of retrieving the value of the reduceId property.
To Top