1826 lines
65 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* PHP_ParserGenerator, a php 5 parser generator.
*
* This is a direct port of the Lemon parser generator, found at
* {@link http://www.hwaci.com/sw/lemon/}
*
* PHP version 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category php
* @package PHP_ParserGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id$
* @since File available since Release 0.1.0
*/
/**
/**
* The state vector for the entire parser generator is recorded in
* this class.
*
* @package PHP_ParserGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version 0.1.0
* @since Class available since Release 0.1.0
*/
class PHP_ParserGenerator_Data
{
/**
* Used for terminal and non-terminal offsets into the action table
* when their default should be used instead
*/
const NO_OFFSET = -2147483647;
/**
* Table of states sorted by state number
* @var array array of {@link PHP_ParserGenerator_State} objects
*/
public $sorted;
/**
* List of all rules
* @var PHP_ParserGenerator_Rule
*/
public $rule;
/**
* Number of states
* @var int
*/
public $nstate;
/**
* Number of rules
* @var int
*/
public $nrule;
/**
* Number of terminal and nonterminal symbols
* @var int
*/
public $nsymbol;
/**
* Number of terminal symbols (tokens)
* @var int
*/
public $nterminal;
/**
* Sorted array of pointers to symbols
* @var array array of {@link PHP_ParserGenerator_Symbol} objects
*/
public $symbols = array();
/**
* Number of errors
* @var int
*/
public $errorcnt;
/**
* The error symbol
* @var PHP_ParserGenerator_Symbol
*/
public $errsym;
/**
* Name of the generated parser
* @var string
*/
public $name;
/**
* Unused relic from the C version
*
* Type of terminal symbols in the parser stack
* @var string
*/
public $tokentype;
/**
* Unused relic from the C version
*
* The default type of non-terminal symbols
* @var string
*/
public $vartype;
/**
* Name of the start symbol for the grammar
* @var string
*/
public $start;
/**
* Size of the parser stack
*
* This is 100 by default, but is set with the %stack_size directive
* @var int
*/
public $stacksize;
/**
* Code to put at the start of the parser file
*
* This is set by the %include directive
* @var string
*/
public $include_code;
/**
* Line number for start of include code
* @var int
*/
public $includeln;
/**
* Code to put in the parser class
*
* This is set by the %include_class directive
* @var string
*/
public $include_classcode;
/**
* Line number for start of include code
* @var int
*/
public $include_classln;
/**
* any extends/implements code
*
* This is set by the %declare_class directive
* @var string
*/
/**
* Line number for class declaration code
* @var int
*/
public $declare_classcode;
/**
* Line number for start of class declaration code
* @var int
*/
public $declare_classln;
/**
* Code to execute when a syntax error is seen
*
* This is set by the %syntax_error directive
* @var string
*/
public $error;
/**
* Line number for start of error code
* @var int
*/
public $errorln;
/**
* Code to execute on a stack overflow
*
* This is set by the %stack_overflow directive
*/
public $overflow;
/**
* Line number for start of overflow code
* @var int
*/
public $overflowln;
/**
* Code to execute on parser failure
*
* This is set by the %parse_failure directive
* @var string
*/
public $failure;
/**
* Line number for start of failure code
* @var int
*/
public $failureln;
/**
* Code to execute when the parser acccepts (completes parsing)
*
* This is set by the %parse_accept directive
* @var string
*/
public $accept;
/**
* Line number for the start of accept code
* @var int
*/
public $acceptln;
/**
* Code appended to the generated file
*
* This is set by the %code directive
* @var string
*/
public $extracode;
/**
* Line number for the start of the extra code
* @var int
*/
public $extracodeln;
/**
* Code to execute to destroy token data
*
* This is set by the %token_destructor directive
* @var string
*/
public $tokendest;
/**
* Line number for token destroyer code
* @var int
*/
public $tokendestln;
/**
* Code for the default non-terminal destructor
*
* This is set by the %default_destructor directive
* @var string
*/
public $vardest;
/**
* Line number for default non-terminal destructor code
* @var int
*/
public $vardestln;
/**
* Name of the input file
* @var string
*/
public $filename;
/**
* Name of the input file without its extension
* @var string
*/
public $filenosuffix;
/**
* Name of the current output file
* @var string
*/
public $outname;
/**
* A prefix added to token names
* @var string
*/
public $tokenprefix;
/**
* Number of parsing conflicts
* @var int
*/
public $nconflict;
/**
* Size of the parse tables
* @var int
*/
public $tablesize;
/**
* Public only basis configurations
*/
public $basisflag;
/**
* True if any %fallback is seen in the grammer
* @var boolean
*/
public $has_fallback;
/**
* Name of the program
* @var string
*/
public $argv0;
/* Find a precedence symbol of every rule in the grammar.
*
* Those rules which have a precedence symbol coded in the input
* grammar using the "[symbol]" construct will already have the
* $rp->precsym field filled. Other rules take as their precedence
* symbol the first RHS symbol with a defined precedence. If there
* are not RHS symbols with a defined precedence, the precedence
* symbol field is left blank.
*/
function FindRulePrecedences()
{
for ($rp = $this->rule; $rp; $rp = $rp->next) {
if ($rp->precsym === 0) {
for ($i = 0; $i < $rp->nrhs && $rp->precsym === 0; $i++) {
$sp = $rp->rhs[$i];
if ($sp->type == PHP_ParserGenerator_Symbol::MULTITERMINAL) {
for ($j = 0; $j < $sp->nsubsym; $j++) {
if ($sp->subsym[$j]->prec >= 0) {
$rp->precsym = $sp->subsym[$j];
break;
}
}
} elseif ($sp->prec >= 0) {
$rp->precsym = $rp->rhs[$i];
}
}
}
}
}
/**
* Find all nonterminals which will generate the empty string.
* Then go back and compute the first sets of every nonterminal.
* The first set is the set of all terminal symbols which can begin
* a string generated by that nonterminal.
*/
function FindFirstSets()
{
for ($i = 0; $i < $this->nsymbol; $i++) {
$this->symbols[$i]->lambda = false;
}
for($i = $this->nterminal; $i < $this->nsymbol; $i++) {
$this->symbols[$i]->firstset = array();
}
/* First compute all lambdas */
do{
$progress = 0;
for ($rp = $this->rule; $rp; $rp = $rp->next) {
if ($rp->lhs->lambda) {
continue;
}
for ($i = 0; $i < $rp->nrhs; $i++) {
$sp = $rp->rhs[$i];
if ($sp->type != PHP_ParserGenerator_Symbol::TERMINAL || $sp->lambda === false) {
break;
}
}
if ($i === $rp->nrhs) {
$rp->lhs->lambda = true;
$progress = 1;
}
}
} while ($progress);
/* Now compute all first sets */
do {
$progress = 0;
for ($rp = $this->rule; $rp; $rp = $rp->next) {
$s1 = $rp->lhs;
for ($i = 0; $i < $rp->nrhs; $i++) {
$s2 = $rp->rhs[$i];
if ($s2->type == PHP_ParserGenerator_Symbol::TERMINAL) {
//progress += SetAdd(s1->firstset,s2->index);
$progress += isset($s1->firstset[$s2->index]) ? 0 : 1;
$s1->firstset[$s2->index] = 1;
break;
} elseif ($s2->type == PHP_ParserGenerator_Symbol::MULTITERMINAL) {
for ($j = 0; $j < $s2->nsubsym; $j++) {
//progress += SetAdd(s1->firstset,s2->subsym[j]->index);
$progress += isset($s1->firstset[$s2->subsym[$j]->index]) ? 0 : 1;
$s1->firstset[$s2->subsym[$j]->index] = 1;
}
break;
} elseif ($s1 === $s2) {
if ($s1->lambda === false) {
break;
}
} else {
//progress += SetUnion(s1->firstset,s2->firstset);
$test = array_diff_key($s2->firstset, $s1->firstset);
if (count($test)) {
$progress++;
$s1->firstset += $test;
}
if ($s2->lambda === false) {
break;
}
}
}
}
} while ($progress);
}
/**
* Compute all LR(0) states for the grammar. Links
* are added to between some states so that the LR(1) follow sets
* can be computed later.
*/
function FindStates()
{
PHP_ParserGenerator_Config::Configlist_init();
/* Find the start symbol */
if ($this->start) {
$sp = PHP_ParserGenerator_Symbol::Symbol_find($this->start);
if ($sp == 0) {
PHP_ParserGenerator::ErrorMsg($this->filename, 0,
"The specified start symbol \"%s\" is not " .
"in a nonterminal of the grammar. \"%s\" will be used as the start " .
"symbol instead.", $this->start, $this->rule->lhs->name);
$this->errorcnt++;
$sp = $this->rule->lhs;
}
} else {
$sp = $this->rule->lhs;
}
/* Make sure the start symbol doesn't occur on the right-hand side of
** any rule. Report an error if it does. (YACC would generate a new
** start symbol in this case.) */
for ($rp = $this->rule; $rp; $rp = $rp->next) {
for ($i = 0; $i < $rp->nrhs; $i++) {
if ($rp->rhs[$i]->type == PHP_ParserGenerator_Symbol::MULTITERMINAL) {
foreach ($rp->rhs[$i]->subsym as $subsp) {
if ($subsp === $sp) {
PHP_ParserGenerator::ErrorMsg($this->filename, 0,
"The start symbol \"%s\" occurs on the " .
"right-hand side of a rule. This will result in a parser which " .
"does not work properly.", $sp->name);
$this->errorcnt++;
}
}
} elseif ($rp->rhs[$i] === $sp) {
PHP_ParserGenerator::ErrorMsg($this->filename, 0,
"The start symbol \"%s\" occurs on the " .
"right-hand side of a rule. This will result in a parser which " .
"does not work properly.", $sp->name);
$this->errorcnt++;
}
}
}
/* The basis configuration set for the first state
** is all rules which have the start symbol as their
** left-hand side */
for ($rp = $sp->rule; $rp; $rp = $rp->nextlhs) {
$newcfp = PHP_ParserGenerator_Config::Configlist_addbasis($rp, 0);
$newcfp->fws[0] = 1;
}
/* Compute the first state. All other states will be
** computed automatically during the computation of the first one.
** The returned pointer to the first state is not used. */
$newstp = array();
$newstp = $this->getstate();
if (is_array($newstp)) {
$this->buildshifts($newstp[0]); /* Recursively compute successor states */
}
}
/**
* @return PHP_ParserGenerator_State
*/
private function getstate()
{
/* Extract the sorted basis of the new state. The basis was constructed
** by prior calls to "Configlist_addbasis()". */
PHP_ParserGenerator_Config::Configlist_sortbasis();
$bp = PHP_ParserGenerator_Config::Configlist_basis();
/* Get a state with the same basis */
$stp = PHP_ParserGenerator_State::State_find($bp);
if ($stp) {
/* A state with the same basis already exists! Copy all the follow-set
** propagation links from the state under construction into the
** preexisting state, then return a pointer to the preexisting state */
for($x = $bp, $y = $stp->bp; $x && $y; $x = $x->bp, $y = $y->bp) {
PHP_ParserGenerator_PropagationLink::Plink_copy($y->bplp, $x->bplp);
PHP_ParserGenerator_PropagationLink::Plink_delete($x->fplp);
$x->fplp = $x->bplp = 0;
}
$cfp = PHP_ParserGenerator_Config::Configlist_return();
PHP_ParserGenerator_Config::Configlist_eat($cfp);
} else {
/* This really is a new state. Construct all the details */
PHP_ParserGenerator_Config::Configlist_closure($this); /* Compute the configuration closure */
PHP_ParserGenerator_Config::Configlist_sort(); /* Sort the configuration closure */
$cfp = PHP_ParserGenerator_Config::Configlist_return(); /* Get a pointer to the config list */
$stp = new PHP_ParserGenerator_State; /* A new state structure */
$stp->bp = $bp; /* Remember the configuration basis */
$stp->cfp = $cfp; /* Remember the configuration closure */
$stp->statenum = $this->nstate++; /* Every state gets a sequence number */
$stp->ap = 0; /* No actions, yet. */
PHP_ParserGenerator_State::State_insert($stp, $stp->bp); /* Add to the state table */
// this can't work, recursion is too deep, move it into FindStates()
//$this->buildshifts($stp); /* Recursively compute successor states */
return array($stp);
}
return $stp;
}
/**
* Construct all successor states to the given state. A "successor"
* state is any state which can be reached by a shift action.
* @param PHP_ParserGenerator_Data
* @param PHP_ParserGenerator_State The state from which successors are computed
*/
private function buildshifts(PHP_ParserGenerator_State $stp)
{
// struct config *cfp; /* For looping thru the config closure of "stp" */
// struct config *bcfp; /* For the inner loop on config closure of "stp" */
// struct config *new; /* */
// struct symbol *sp; /* Symbol following the dot in configuration "cfp" */
// struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */
// struct state *newstp; /* A pointer to a successor state */
/* Each configuration becomes complete after it contibutes to a successor
** state. Initially, all configurations are incomplete */
$cfp = $stp->cfp;
for ($cfp = $stp->cfp; $cfp; $cfp = $cfp->next) {
$cfp->status = PHP_ParserGenerator_Config::INCOMPLETE;
}
/* Loop through all configurations of the state "stp" */
for ($cfp = $stp->cfp; $cfp; $cfp = $cfp->next) {
if ($cfp->status == PHP_ParserGenerator_Config::COMPLETE) {
continue; /* Already used by inner loop */
}
if ($cfp->dot >= $cfp->rp->nrhs) {
continue; /* Can't shift this config */
}
PHP_ParserGenerator_Config::Configlist_reset(); /* Reset the new config set */
$sp = $cfp->rp->rhs[$cfp->dot]; /* Symbol after the dot */
/* For every configuration in the state "stp" which has the symbol "sp"
** following its dot, add the same configuration to the basis set under
** construction but with the dot shifted one symbol to the right. */
$bcfp = $cfp;
for ($bcfp = $cfp; $bcfp; $bcfp = $bcfp->next) {
if ($bcfp->status == PHP_ParserGenerator_Config::COMPLETE) {
continue; /* Already used */
}
if ($bcfp->dot >= $bcfp->rp->nrhs) {
continue; /* Can't shift this one */
}
$bsp = $bcfp->rp->rhs[$bcfp->dot]; /* Get symbol after dot */
if (!PHP_ParserGenerator_Symbol::same_symbol($bsp, $sp)) {
continue; /* Must be same as for "cfp" */
}
$bcfp->status = PHP_ParserGenerator_Config::COMPLETE; /* Mark this config as used */
$new = PHP_ParserGenerator_Config::Configlist_addbasis($bcfp->rp, $bcfp->dot + 1);
PHP_ParserGenerator_PropagationLink::Plink_add($new->bplp, $bcfp);
}
/* Get a pointer to the state described by the basis configuration set
** constructed in the preceding loop */
$newstp = $this->getstate();
if (is_array($newstp)) {
$this->buildshifts($newstp[0]); /* Recursively compute successor states */
$newstp = $newstp[0];
}
/* The state "newstp" is reached from the state "stp" by a shift action
** on the symbol "sp" */
if ($sp->type == PHP_ParserGenerator_Symbol::MULTITERMINAL) {
for($i = 0; $i < $sp->nsubsym; $i++) {
PHP_ParserGenerator_Action::Action_add($stp->ap, PHP_ParserGenerator_Action::SHIFT, $sp->subsym[$i],
$newstp);
}
} else {
PHP_ParserGenerator_Action::Action_add($stp->ap, PHP_ParserGenerator_Action::SHIFT, $sp, $newstp);
}
}
}
/**
* Construct the propagation links
*/
function FindLinks()
{
/* Housekeeping detail:
** Add to every propagate link a pointer back to the state to
** which the link is attached. */
foreach ($this->sorted as $info) {
$info->key->stp = $info->data;
}
/* Convert all backlinks into forward links. Only the forward
** links are used in the follow-set computation. */
for ($i = 0; $i < $this->nstate; $i++) {
$stp = $this->sorted[$i];
for ($cfp = $stp->data->cfp; $cfp; $cfp = $cfp->next) {
for ($plp = $cfp->bplp; $plp; $plp = $plp->next) {
$other = $plp->cfp;
PHP_ParserGenerator_PropagationLink::Plink_add($other->fplp, $cfp);
}
}
}
}
/**
* Compute the reduce actions, and resolve conflicts.
*/
function FindActions()
{
/* Add all of the reduce actions
** A reduce action is added for each element of the followset of
** a configuration which has its dot at the extreme right.
*/
for ($i = 0; $i < $this->nstate; $i++) { /* Loop over all states */
$stp = $this->sorted[$i]->data;
for ($cfp = $stp->cfp; $cfp; $cfp = $cfp->next) {
/* Loop over all configurations */
if ($cfp->rp->nrhs == $cfp->dot) { /* Is dot at extreme right? */
for ($j = 0; $j < $this->nterminal; $j++) {
if (isset($cfp->fws[$j])) {
/* Add a reduce action to the state "stp" which will reduce by the
** rule "cfp->rp" if the lookahead symbol is "$this->symbols[j]" */
PHP_ParserGenerator_Action::Action_add($stp->ap, PHP_ParserGenerator_Action::REDUCE,
$this->symbols[$j], $cfp->rp);
}
}
}
}
}
/* Add the accepting token */
if ($this->start instanceof PHP_ParserGenerator_Symbol) {
$sp = PHP_ParserGenerator_Symbol::Symbol_find($this->start);
if ($sp === 0) {
$sp = $this->rule->lhs;
}
} else {
$sp = $this->rule->lhs;
}
/* Add to the first state (which is always the starting state of the
** finite state machine) an action to ACCEPT if the lookahead is the
** start nonterminal. */
PHP_ParserGenerator_Action::Action_add($this->sorted[0]->data->ap, PHP_ParserGenerator_Action::ACCEPT, $sp, 0);
/* Resolve conflicts */
for ($i = 0; $i < $this->nstate; $i++) {
// struct action *ap, *nap;
// struct state *stp;
$stp = $this->sorted[$i]->data;
if (!$stp->ap) {
throw new Exception('state has no actions associated');
}
$stp->ap = PHP_ParserGenerator_Action::Action_sort($stp->ap);
for ($ap = $stp->ap; $ap !== 0 && $ap->next !== 0; $ap = $ap->next) {
for ($nap = $ap->next; $nap !== 0 && $nap->sp === $ap->sp ; $nap = $nap->next) {
/* The two actions "ap" and "nap" have the same lookahead.
** Figure out which one should be used */
$this->nconflict += $this->resolve_conflict($ap, $nap, $this->errsym);
}
}
}
/* Report an error for each rule that can never be reduced. */
for ($rp = $this->rule; $rp; $rp = $rp->next) {
$rp->canReduce = false;
}
for ($i = 0; $i < $this->nstate; $i++) {
for ($ap = $this->sorted[$i]->data->ap; $ap !== 0; $ap = $ap->next) {
if ($ap->type == PHP_ParserGenerator_Action::REDUCE) {
$ap->x->canReduce = true;
}
}
}
for ($rp = $this->rule; $rp !== 0; $rp = $rp->next) {
if ($rp->canReduce) {
continue;
}
PHP_ParserGenerator::ErrorMsg($this->filename, $rp->ruleline, "This rule can not be reduced (is not connected to the start symbol).\n");
$this->errorcnt++;
}
}
/** Resolve a conflict between the two given actions. If the
* conflict can't be resolve, return non-zero.
*
* NO LONGER TRUE:
* To resolve a conflict, first look to see if either action
* is on an error rule. In that case, take the action which
* is not associated with the error rule. If neither or both
* actions are associated with an error rule, then try to
* use precedence to resolve the conflict.
*
* If either action is a SHIFT, then it must be apx. This
* function won't work if apx->type==REDUCE and apy->type==SHIFT.
* @param PHP_ParserGenerator_Action
* @param PHP_ParserGenerator_Action
* @param PHP_ParserGenerator_Symbol|null The error symbol (if defined. NULL otherwise)
*/
function resolve_conflict($apx, $apy, $errsym)
{
$errcnt = 0;
if ($apx->sp !== $apy->sp) {
throw new Exception('no conflict but resolve_conflict called');
}
if ($apx->type == PHP_ParserGenerator_Action::SHIFT && $apy->type == PHP_ParserGenerator_Action::REDUCE) {
$spx = $apx->sp;
$spy = $apy->x->precsym;
if ($spy === 0 || $spx->prec < 0 || $spy->prec < 0) {
/* Not enough precedence information. */
$apy->type = PHP_ParserGenerator_Action::CONFLICT;
$errcnt++;
} elseif ($spx->prec > $spy->prec) { /* Lower precedence wins */
$apy->type = PHP_ParserGenerator_Action::RD_RESOLVED;
} elseif ($spx->prec < $spy->prec) {
$apx->type = PHP_ParserGenerator_Action::SH_RESOLVED;
} elseif ($spx->prec === $spy->prec && $spx->assoc == PHP_ParserGenerator_Symbol::RIGHT) {
/* Use operator */
$apy->type = PHP_ParserGenerator_Action::RD_RESOLVED; /* associativity */
} elseif ($spx->prec === $spy->prec && $spx->assoc == PHP_ParserGenerator_Symbol::LEFT) {
/* to break tie */
$apx->type = PHP_ParserGenerator_Action::SH_RESOLVED;
} else {
if ($spx->prec !== $spy->prec || $spx->assoc !== PHP_ParserGenerator_Symbol::NONE) {
throw new Exception('$spx->prec !== $spy->prec || $spx->assoc !== PHP_ParserGenerator_Symbol::NONE');
}
$apy->type = PHP_ParserGenerator_Action::CONFLICT;
$errcnt++;
}
} elseif ($apx->type == PHP_ParserGenerator_Action::REDUCE && $apy->type == PHP_ParserGenerator_Action::REDUCE) {
$spx = $apx->x->precsym;
$spy = $apy->x->precsym;
if ($spx === 0 || $spy === 0 || $spx->prec < 0 ||
$spy->prec < 0 || $spx->prec === $spy->prec) {
$apy->type = PHP_ParserGenerator_Action::CONFLICT;
$errcnt++;
} elseif ($spx->prec > $spy->prec) {
$apy->type = PHP_ParserGenerator_Action::RD_RESOLVED;
} elseif ($spx->prec < $spy->prec) {
$apx->type = PHP_ParserGenerator_Action::RD_RESOLVED;
}
} else {
if ($apx->type!== PHP_ParserGenerator_Action::SH_RESOLVED &&
$apx->type!== PHP_ParserGenerator_Action::RD_RESOLVED &&
$apx->type!== PHP_ParserGenerator_Action::CONFLICT &&
$apy->type!== PHP_ParserGenerator_Action::SH_RESOLVED &&
$apy->type!== PHP_ParserGenerator_Action::RD_RESOLVED &&
$apy->type!== PHP_ParserGenerator_Action::CONFLICT) {
throw new Exception('$apx->type!== PHP_ParserGenerator_Action::SH_RESOLVED &&
$apx->type!== PHP_ParserGenerator_Action::RD_RESOLVED &&
$apx->type!== PHP_ParserGenerator_Action::CONFLICT &&
$apy->type!== PHP_ParserGenerator_Action::SH_RESOLVED &&
$apy->type!== PHP_ParserGenerator_Action::RD_RESOLVED &&
$apy->type!== PHP_ParserGenerator_Action::CONFLICT');
}
/* The REDUCE/SHIFT case cannot happen because SHIFTs come before
** REDUCEs on the list. If we reach this point it must be because
** the parser conflict had already been resolved. */
}
return $errcnt;
}
/**
* Reduce the size of the action tables, if possible, by making use
* of defaults.
*
* In this version, we take the most frequent REDUCE action and make
* it the default.
*/
function CompressTables()
{
for ($i = 0; $i < $this->nstate; $i++) {
$stp = $this->sorted[$i]->data;
$nbest = 0;
$rbest = 0;
for ($ap = $stp->ap; $ap; $ap = $ap->next) {
if ($ap->type != PHP_ParserGenerator_Action::REDUCE) {
continue;
}
$rp = $ap->x;
if ($rp === $rbest) {
continue;
}
$n = 1;
for ($ap2 = $ap->next; $ap2; $ap2 = $ap2->next) {
if ($ap2->type != PHP_ParserGenerator_Action::REDUCE) {
continue;
}
$rp2 = $ap2->x;
if ($rp2 === $rbest) {
continue;
}
if ($rp2 === $rp) {
$n++;
}
}
if ($n > $nbest) {
$nbest = $n;
$rbest = $rp;
}
}
/* Do not make a default if the number of rules to default
** is not at least 1 */
if ($nbest < 1) {
continue;
}
/* Combine matching REDUCE actions into a single default */
for ($ap = $stp->ap; $ap; $ap = $ap->next) {
if ($ap->type == PHP_ParserGenerator_Action::REDUCE && $ap->x === $rbest) {
break;
}
}
if ($ap === 0) {
throw new Exception('$ap is not an object');
}
$ap->sp = PHP_ParserGenerator_Symbol::Symbol_new("{default}");
for ($ap = $ap->next; $ap; $ap = $ap->next) {
if ($ap->type == PHP_ParserGenerator_Action::REDUCE && $ap->x === $rbest) {
$ap->type = PHP_ParserGenerator_Action::NOT_USED;
}
}
$stp->ap = PHP_ParserGenerator_Action::Action_sort($stp->ap);
}
}
/**
* Renumber and resort states so that states with fewer choices
* occur at the end. Except, keep state 0 as the first state.
*/
function ResortStates()
{
for ($i = 0; $i < $this->nstate; $i++) {
$stp = $this->sorted[$i]->data;
$stp->nTknAct = $stp->nNtAct = 0;
$stp->iDflt = $this->nstate + $this->nrule;
$stp->iTknOfst = PHP_ParserGenerator_Data::NO_OFFSET;
$stp->iNtOfst = PHP_ParserGenerator_Data::NO_OFFSET;
for ($ap = $stp->ap; $ap; $ap = $ap->next) {
if ($this->compute_action($ap) >= 0) {
if ($ap->sp->index < $this->nterminal) {
$stp->nTknAct++;
} elseif ($ap->sp->index < $this->nsymbol) {
$stp->nNtAct++;
} else {
$stp->iDflt = $this->compute_action($ap);
}
}
}
$this->sorted[$i] = $stp;
}
$save = $this->sorted[0];
unset($this->sorted[0]);
usort($this->sorted, array('PHP_ParserGenerator_State', 'stateResortCompare'));
array_unshift($this->sorted, $save);
for($i = 0; $i < $this->nstate; $i++) {
$this->sorted[$i]->statenum = $i;
}
}
/**
* Given an action, compute the integer value for that action
* which is to be put in the action table of the generated machine.
* Return negative if no action should be generated.
* @param PHP_ParserGenerator_Action
*/
function compute_action($ap)
{
switch ($ap->type) {
case PHP_ParserGenerator_Action::SHIFT:
$act = $ap->x->statenum;
break;
case PHP_ParserGenerator_Action::REDUCE:
$act = $ap->x->index + $this->nstate;
break;
case PHP_ParserGenerator_Action::ERROR:
$act = $this->nstate + $this->nrule;
break;
case PHP_ParserGenerator_Action::ACCEPT:
$act = $this->nstate + $this->nrule + 1;
break;
default:
$act = -1;
break;
}
return $act;
}
/**
* Generate the "Parse.out" log file
*/
function ReportOutput()
{
$fp = fopen($this->filenosuffix . ".out", "wb");
if (!$fp) {
return;
}
for ($i = 0; $i < $this->nstate; $i++) {
$stp = $this->sorted[$i];
fprintf($fp, "State %d:\n", $stp->statenum);
if ($this->basisflag) {
$cfp = $stp->bp;
} else {
$cfp = $stp->cfp;
}
while ($cfp) {
if ($cfp->dot == $cfp->rp->nrhs) {
$buf = sprintf('(%d)', $cfp->rp->index);
fprintf($fp, ' %5s ', $buf);
} else {
fwrite($fp,' ');
}
$cfp->ConfigPrint($fp);
fwrite($fp, "\n");
if (0) {
//SetPrint(fp,cfp->fws,$this);
//PlinkPrint(fp,cfp->fplp,"To ");
//PlinkPrint(fp,cfp->bplp,"From");
}
if ($this->basisflag) {
$cfp = $cfp->bp;
} else {
$cfp = $cfp->next;
}
}
fwrite($fp, "\n");
for ($ap = $stp->ap; $ap; $ap = $ap->next) {
if ($ap->PrintAction($fp, 30)) {
fprintf($fp,"\n");
}
}
fwrite($fp,"\n");
}
fclose($fp);
}
/**
* The next function finds the template file and opens it, returning
* a pointer to the opened file.
* @return resource
*/
private function tplt_open()
{
$templatename = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . "Lempar.php";
$buf = $this->filenosuffix . '.lt';
if (file_exists($buf) && is_readable($buf)) {
$tpltname = $buf;
} elseif (file_exists($templatename) && is_readable($templatename)) {
$tpltname = $templatename;
} elseif ($fp = @fopen($templatename, 'rb', true)) {
return $fp;
}
if (!isset($tpltname)) {
echo "Can't find the parser driver template file \"%s\".\n",
$templatename;
$this->errorcnt++;
return 0;
}
$in = @fopen($tpltname,"rb");
if (!$in) {
printf("Can't open the template file \"%s\".\n", $tpltname);
$this->errorcnt++;
return 0;
}
return $in;
}
#define LINESIZE 1000
/**#@+
* The next cluster of routines are for reading the template file
* and writing the results to the generated parser
*/
/**
* The first function transfers data from "in" to "out" until
* a line is seen which begins with "%%". The line number is
* tracked.
*
* if name!=0, then any word that begin with "Parse" is changed to
* begin with *name instead.
*/
private function tplt_xfer($name, $in, $out, &$lineno)
{
while (($line = fgets($in, 1024)) && ($line[0] != '%' || $line[1] != '%')) {
$lineno++;
$iStart = 0;
if ($name) {
for ($i = 0; $i < strlen($line); $i++) {
if ($line[$i] == 'P' && substr($line, $i, 5) == "Parse"
&& ($i === 0 || preg_match('/[^a-zA-Z]/', $line[$i - 1]))) {
if ($i > $iStart) {
fwrite($out, substr($line, $iStart, $i - $iStart));
}
fwrite($out, $name);
$i += 4;
$iStart = $i + 1;
}
}
}
fwrite($out, substr($line, $iStart));
}
}
/**
* Print a #line directive line to the output file.
*/
private function tplt_linedir($out, $lineno, $filename)
{
fwrite($out, '#line ' . $lineno . ' "' . $filename . "\"\n");
}
/**
* Print a string to the file and keep the linenumber up to date
*/
private function tplt_print($out, $str, $strln, &$lineno)
{
if ($str == '') {
return;
}
$this->tplt_linedir($out, $strln, $this->filename);
$lineno++;
fwrite($out, $str);
$lineno += count(explode("\n", $str)) - 1;
$this->tplt_linedir($out, $lineno + 2, $this->outname);
$lineno += 2;
}
/**#@-*/
/**
* Compute all followsets.
*
* A followset is the set of all symbols which can come immediately
* after a configuration.
*/
function FindFollowSets()
{
for ($i = 0; $i < $this->nstate; $i++) {
for ($cfp = $this->sorted[$i]->data->cfp; $cfp; $cfp = $cfp->next) {
$cfp->status = PHP_ParserGenerator_Config::INCOMPLETE;
}
}
do {
$progress = 0;
for ($i = 0; $i < $this->nstate; $i++) {
for ($cfp = $this->sorted[$i]->data->cfp; $cfp; $cfp = $cfp->next) {
if ($cfp->status == PHP_ParserGenerator_Config::COMPLETE) {
continue;
}
for ($plp = $cfp->fplp; $plp; $plp = $plp->next) {
$a = array_diff_key($cfp->fws, $plp->cfp->fws);
if (count($a)) {
$plp->cfp->fws += $a;
$plp->cfp->status = PHP_ParserGenerator_Config::INCOMPLETE;
$progress = 1;
}
}
$cfp->status = PHP_ParserGenerator_Config::COMPLETE;
}
}
} while ($progress);
}
/**
* Generate C source code for the parser
* @param int Output in makeheaders format if true
*/
function ReportTable($mhflag)
{
// FILE *out, *in;
// char line[LINESIZE];
// int lineno;
// struct state *stp;
// struct action *ap;
// struct rule *rp;
// struct acttab *pActtab;
// int i, j, n;
// char *name;
// int mnTknOfst, mxTknOfst;
// int mnNtOfst, mxNtOfst;
// struct axset *ax;
$in = $this->tplt_open();
if (!$in) {
return;
}
$out = fopen($this->filenosuffix . ".php", "wb");
if (!$out) {
fclose($in);
return;
}
$this->outname = $this->filenosuffix . ".php";
$lineno = 1;
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate the include code, if any */
$this->tplt_print($out, $this->include_code, $this->includeln, $lineno);
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate the class declaration code */
$this->tplt_print($out, $this->declare_classcode, $this->declare_classln,
$lineno);
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate the internal parser class include code, if any */
$this->tplt_print($out, $this->include_classcode, $this->include_classln,
$lineno);
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate #defines for all tokens */
//if ($mhflag) {
//fprintf($out, "#if INTERFACE\n");
$lineno++;
if ($this->tokenprefix) {
$prefix = $this->tokenprefix;
} else {
$prefix = '';
}
for ($i = 1; $i < $this->nterminal; $i++) {
fprintf($out, " const %s%-30s = %2d;\n", $prefix, $this->symbols[$i]->name, $i);
$lineno++;
}
//fwrite($out, "#endif\n");
$lineno++;
//}
fwrite($out, " const YY_NO_ACTION = " .
($this->nstate + $this->nrule + 2) . ";\n");
$lineno++;
fwrite($out, " const YY_ACCEPT_ACTION = " .
($this->nstate + $this->nrule + 1) . ";\n");
$lineno++;
fwrite($out, " const YY_ERROR_ACTION = " .
($this->nstate + $this->nrule) . ";\n");
$lineno++;
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate the action table and its associates:
**
** yy_action[] A single table containing all actions.
** yy_lookahead[] A table containing the lookahead for each entry in
** yy_action. Used to detect hash collisions.
** yy_shift_ofst[] For each state, the offset into yy_action for
** shifting terminals.
** yy_reduce_ofst[] For each state, the offset into yy_action for
** shifting non-terminals after a reduce.
** yy_default[] Default action for each state.
*/
/* Compute the actions on all states and count them up */
$ax = array();
for ($i = 0; $i < $this->nstate; $i++) {
$stp = $this->sorted[$i];
$ax[$i * 2] = array();
$ax[$i * 2]['stp'] = $stp;
$ax[$i * 2]['isTkn'] = 1;
$ax[$i * 2]['nAction'] = $stp->nTknAct;
$ax[$i * 2 + 1] = array();
$ax[$i * 2 + 1]['stp'] = $stp;
$ax[$i * 2 + 1]['isTkn'] = 0;
$ax[$i * 2 + 1]['nAction'] = $stp->nNtAct;
}
$mxTknOfst = $mnTknOfst = 0;
$mxNtOfst = $mnNtOfst = 0;
/* Compute the action table. In order to try to keep the size of the
** action table to a minimum, the heuristic of placing the largest action
** sets first is used.
*/
usort($ax, array('PHP_ParserGenerator_Data', 'axset_compare'));
$pActtab = new PHP_ParserGenerator_ActionTable;
for ($i = 0; $i < $this->nstate * 2 && $ax[$i]['nAction'] > 0; $i++) {
$stp = $ax[$i]['stp'];
if ($ax[$i]['isTkn']) {
for ($ap = $stp->ap; $ap; $ap = $ap->next) {
if ($ap->sp->index >= $this->nterminal) {
continue;
}
$action = $this->compute_action($ap);
if ($action < 0) {
continue;
}
$pActtab->acttab_action($ap->sp->index, $action);
}
$stp->iTknOfst = $pActtab->acttab_insert();
if ($stp->iTknOfst < $mnTknOfst) {
$mnTknOfst = $stp->iTknOfst;
}
if ($stp->iTknOfst > $mxTknOfst) {
$mxTknOfst = $stp->iTknOfst;
}
} else {
for ($ap = $stp->ap; $ap; $ap = $ap->next) {
if ($ap->sp->index < $this->nterminal) {
continue;
}
if ($ap->sp->index == $this->nsymbol) {
continue;
}
$action = $this->compute_action($ap);
if ($action < 0) {
continue;
}
$pActtab->acttab_action($ap->sp->index, $action);
}
$stp->iNtOfst = $pActtab->acttab_insert();
if ($stp->iNtOfst < $mnNtOfst) {
$mnNtOfst = $stp->iNtOfst;
}
if ($stp->iNtOfst > $mxNtOfst) {
$mxNtOfst = $stp->iNtOfst;
}
}
}
/* Output the yy_action table */
fprintf($out, " const YY_SZ_ACTTAB = %d;\n", $pActtab->nAction);
$lineno++;
fwrite($out, "static public \$yy_action = array(\n");
$lineno++;
$n = $pActtab->nAction;
for($i = $j = 0; $i < $n; $i++) {
$action = $pActtab->aAction[$i]['action'];
if ($action < 0) {
$action = $this->nsymbol + $this->nrule + 2;
}
// change next line
if ($j === 0) {
fprintf($out, " /* %5d */ ", $i);
}
fprintf($out, " %4d,", $action);
if ($j == 9 || $i == $n - 1) {
fwrite($out, "\n");
$lineno++;
$j = 0;
} else {
$j++;
}
}
fwrite($out, " );\n"); $lineno++;
/* Output the yy_lookahead table */
fwrite($out, " static public \$yy_lookahead = array(\n");
$lineno++;
for ($i = $j = 0; $i < $n; $i++) {
$la = $pActtab->aAction[$i]['lookahead'];
if ($la < 0) {
$la = $this->nsymbol;
}
// change next line
if ($j === 0) {
fprintf($out, " /* %5d */ ", $i);
}
fprintf($out, " %4d,", $la);
if ($j == 9 || $i == $n - 1) {
fwrite($out, "\n");
$lineno++;
$j = 0;
} else {
$j++;
}
}
fwrite($out, ");\n");
$lineno++;
/* Output the yy_shift_ofst[] table */
fprintf($out, " const YY_SHIFT_USE_DFLT = %d;\n", $mnTknOfst - 1);
$lineno++;
$n = $this->nstate;
while ($n > 0 && $this->sorted[$n - 1]->iTknOfst == PHP_ParserGenerator_Data::NO_OFFSET) {
$n--;
}
fprintf($out, " const YY_SHIFT_MAX = %d;\n", $n - 1);
$lineno++;
fwrite($out, " static public \$yy_shift_ofst = array(\n");
$lineno++;
for ($i = $j = 0; $i < $n; $i++) {
$stp = $this->sorted[$i];
$ofst = $stp->iTknOfst;
if ($ofst === PHP_ParserGenerator_Data::NO_OFFSET) {
$ofst = $mnTknOfst - 1;
}
// change next line
if ($j === 0) {
fprintf($out, " /* %5d */ ", $i);
}
fprintf($out, " %4d,", $ofst);
if ($j == 9 || $i == $n - 1) {
fwrite($out, "\n");
$lineno++;
$j = 0;
} else {
$j++;
}
}
fwrite($out, ");\n");
$lineno++;
/* Output the yy_reduce_ofst[] table */
fprintf($out, " const YY_REDUCE_USE_DFLT = %d;\n", $mnNtOfst - 1);
$lineno++;
$n = $this->nstate;
while ($n > 0 && $this->sorted[$n - 1]->iNtOfst == PHP_ParserGenerator_Data::NO_OFFSET) {
$n--;
}
fprintf($out, " const YY_REDUCE_MAX = %d;\n", $n - 1);
$lineno++;
fwrite($out, " static public \$yy_reduce_ofst = array(\n");
$lineno++;
for ($i = $j = 0; $i < $n; $i++) {
$stp = $this->sorted[$i];
$ofst = $stp->iNtOfst;
if ($ofst == PHP_ParserGenerator_Data::NO_OFFSET) {
$ofst = $mnNtOfst - 1;
}
// change next line
if ($j == 0) {
fprintf($out, " /* %5d */ ", $i);
}
fprintf($out, " %4d,", $ofst);
if ($j == 9 || $i == $n - 1) {
fwrite($out, "\n");
$lineno++;
$j = 0;
} else {
$j++;
}
}
fwrite($out, ");\n");
$lineno++;
/* Output the expected tokens table */
fwrite($out, " static public \$yyExpectedTokens = array(\n");
$lineno++;
for ($i = 0; $i < $this->nstate; $i++) {
$stp = $this->sorted[$i];
fwrite($out, " /* $i */ array(");
for ($ap = $stp->ap; $ap; $ap = $ap->next) {
if ($ap->sp->index < $this->nterminal) {
if ($ap->type == PHP_ParserGenerator_Action::SHIFT ||
$ap->type == PHP_ParserGenerator_Action::REDUCE) {
fwrite($out, $ap->sp->index . ', ');
}
}
}
fwrite($out, "),\n");
$lineno++;
}
fwrite($out, ");\n");
$lineno++;
/* Output the default action table */
fwrite($out, " static public \$yy_default = array(\n");
$lineno++;
$n = $this->nstate;
for ($i = $j = 0; $i < $n; $i++) {
$stp = $this->sorted[$i];
// change next line
if ($j == 0) {
fprintf($out, " /* %5d */ ", $i);
}
fprintf($out, " %4d,", $stp->iDflt);
if ($j == 9 || $i == $n - 1) {
fprintf($out, "\n"); $lineno++;
$j = 0;
} else {
$j++;
}
}
fwrite($out, ");\n");
$lineno++;
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate the defines */
fprintf($out, " const YYNOCODE = %d;\n", $this->nsymbol + 1);
$lineno++;
if ($this->stacksize) {
if($this->stacksize <= 0) {
PHP_ParserGenerator::ErrorMsg($this->filename, 0,
"Illegal stack size: [%s]. The stack size should be an integer constant.",
$this->stacksize);
$this->errorcnt++;
$this->stacksize = "100";
}
fprintf($out, " const YYSTACKDEPTH = %s;\n", $this->stacksize);
$lineno++;
} else {
fwrite($out," const YYSTACKDEPTH = 100;\n");
$lineno++;
}
fprintf($out, " const YYNSTATE = %d;\n", $this->nstate);
$lineno++;
fprintf($out, " const YYNRULE = %d;\n", $this->nrule);
$lineno++;
fprintf($out, " const YYERRORSYMBOL = %d;\n", $this->errsym->index);
$lineno++;
fprintf($out, " const YYERRSYMDT = 'yy%d';\n", $this->errsym->dtnum);
$lineno++;
if ($this->has_fallback) {
fwrite($out, " const YYFALLBACK = 1;\n");
} else {
fwrite($out, " const YYFALLBACK = 0;\n");
}
$lineno++;
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate the table of fallback tokens.
*/
if ($this->has_fallback) {
for ($i = 0; $i < $this->nterminal; $i++) {
$p = $this->symbols[$i];
if ($p->fallback === 0) {
// change next line
fprintf($out, " 0, /* %10s => nothing */\n", $p->name);
} else {
// change next line
fprintf($out, " %3d, /* %10s => %s */\n",
$p->fallback->index, $p->name, $p->fallback->name);
}
$lineno++;
}
}
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate a table containing the symbolic name of every symbol
($yyTokenName)
*/
for ($i = 0; $i < $this->nsymbol; $i++) {
fprintf($out," %-15s", "'" . $this->symbols[$i]->name . "',");
if (($i & 3) == 3) {
fwrite($out,"\n");
$lineno++;
}
}
if (($i & 3) != 0) {
fwrite($out, "\n");
$lineno++;
}
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate a table containing a text string that describes every
** rule in the rule set of the grammer. This information is used
** when tracing REDUCE actions.
*/
for ($i = 0, $rp = $this->rule; $rp; $rp = $rp->next, $i++) {
if ($rp->index !== $i) {
throw new Exception('rp->index != i and should be');
}
// change next line
fprintf($out, " /* %3d */ \"%s ::=", $i, $rp->lhs->name);
for ($j = 0; $j < $rp->nrhs; $j++) {
$sp = $rp->rhs[$j];
fwrite($out,' ' . $sp->name);
if ($sp->type == PHP_ParserGenerator_Symbol::MULTITERMINAL) {
for($k = 1; $k < $sp->nsubsym; $k++) {
fwrite($out, '|' . $sp->subsym[$k]->name);
}
}
}
fwrite($out, "\",\n");
$lineno++;
}
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate code which executes every time a symbol is popped from
** the stack while processing errors or while destroying the parser.
** (In other words, generate the %destructor actions)
*/
if ($this->tokendest) {
for ($i = 0; $i < $this->nsymbol; $i++) {
$sp = $this->symbols[$i];
if ($sp === 0 || $sp->type != PHP_ParserGenerator_Symbol::TERMINAL) {
continue;
}
fprintf($out, " case %d:\n", $sp->index);
$lineno++;
}
for ($i = 0; $i < $this->nsymbol &&
$this->symbols[$i]->type != PHP_ParserGenerator_Symbol::TERMINAL; $i++);
if ($i < $this->nsymbol) {
$this->emit_destructor_code($out, $this->symbols[$i], $lineno);
fprintf($out, " break;\n");
$lineno++;
}
}
if ($this->vardest) {
$dflt_sp = 0;
for ($i = 0; $i < $this->nsymbol; $i++) {
$sp = $this->symbols[$i];
if ($sp === 0 || $sp->type == PHP_ParserGenerator_Symbol::TERMINAL ||
$sp->index <= 0 || $sp->destructor != 0) {
continue;
}
fprintf($out, " case %d:\n", $sp->index);
$lineno++;
$dflt_sp = $sp;
}
if ($dflt_sp != 0) {
$this->emit_destructor_code($out, $dflt_sp, $lineno);
fwrite($out, " break;\n");
$lineno++;
}
}
for ($i = 0; $i < $this->nsymbol; $i++) {
$sp = $this->symbols[$i];
if ($sp === 0 || $sp->type == PHP_ParserGenerator_Symbol::TERMINAL ||
$sp->destructor === 0) {
continue;
}
fprintf($out, " case %d:\n", $sp->index);
$lineno++;
/* Combine duplicate destructors into a single case */
for ($j = $i + 1; $j < $this->nsymbol; $j++) {
$sp2 = $this->symbols[$j];
if ($sp2 && $sp2->type != PHP_ParserGenerator_Symbol::TERMINAL && $sp2->destructor
&& $sp2->dtnum == $sp->dtnum
&& $sp->destructor == $sp2->destructor) {
fprintf($out, " case %d:\n", $sp2->index);
$lineno++;
$sp2->destructor = 0;
}
}
$this->emit_destructor_code($out, $this->symbols[$i], $lineno);
fprintf($out, " break;\n");
$lineno++;
}
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate code which executes whenever the parser stack overflows */
$this->tplt_print($out, $this->overflow, $this->overflowln, $lineno);
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate the table of rule information
**
** Note: This code depends on the fact that rules are number
** sequentually beginning with 0.
*/
for ($rp = $this->rule; $rp; $rp = $rp->next) {
fprintf($out, " array( 'lhs' => %d, 'rhs' => %d ),\n",
$rp->lhs->index, $rp->nrhs);
$lineno++;
}
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate code which executes during each REDUCE action */
for ($rp = $this->rule; $rp; $rp = $rp->next) {
if ($rp->code) {
$this->translate_code($rp);
}
}
/* Generate the method map for each REDUCE action */
for ($rp = $this->rule; $rp; $rp = $rp->next) {
if ($rp->code === 0) {
continue;
}
fwrite($out, ' ' . $rp->index . ' => ' . $rp->index . ",\n");
$lineno++;
for ($rp2 = $rp->next; $rp2; $rp2 = $rp2->next) {
if ($rp2->code === $rp->code) {
fwrite($out, ' ' . $rp2->index . ' => ' .
$rp->index . ",\n");
$lineno++;
$rp2->code = 0;
}
}
}
$this->tplt_xfer($this->name, $in, $out, $lineno);
for ($rp = $this->rule; $rp; $rp = $rp->next) {
if ($rp->code === 0) {
continue;
}
$this->emit_code($out, $rp, $lineno);
}
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate code which executes if a parse fails */
$this->tplt_print($out, $this->failure, $this->failureln, $lineno);
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate code which executes when a syntax error occurs */
$this->tplt_print($out, $this->error, $this->errorln, $lineno);
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Generate code which executes when the parser accepts its input */
$this->tplt_print($out, $this->accept, $this->acceptln, $lineno);
$this->tplt_xfer($this->name, $in, $out, $lineno);
/* Append any addition code the user desires */
$this->tplt_print($out, $this->extracode, $this->extracodeln, $lineno);
fclose($in);
fclose($out);
}
/**
* Generate code which executes when the rule "rp" is reduced. Write
* the code to "out". Make sure lineno stays up-to-date.
*/
function emit_code($out, PHP_ParserGenerator_Rule $rp, &$lineno)
{
$linecnt = 0;
/* Generate code to do the reduce action */
if ($rp->code) {
$this->tplt_linedir($out, $rp->line, $this->filename);
fwrite($out, " function yy_r$rp->index(){" . $rp->code);
$linecnt += count(explode("\n", $rp->code)) - 1;
$lineno += 3 + $linecnt;
fwrite($out, " }\n");
$this->tplt_linedir($out, $lineno, $this->outname);
} /* End if( rp->code ) */
}
/**
* Append text to a dynamically allocated string. If zText is 0 then
* reset the string to be empty again. Always return the complete text
* of the string (which is overwritten with each call).
*
* n bytes of zText are stored. If n==0 then all of zText is stored.
*
* If n==-1, then the previous character is overwritten.
* @param string
* @param int
*/
function append_str($zText, $n)
{
static $z = '';
$zInt = '';
if ($zText === '') {
$ret = $z;
$z = '';
return $ret;
}
if ($n <= 0) {
if ($n < 0) {
if (!strlen($z)) {
throw new Exception('z is zero-length');
}
$z = substr($z, 0, strlen($z) - 1);
if (!$z) {
$z = '';
}
}
$n = strlen($zText);
}
$i = 0;
$z .= substr($zText, 0, $n);
return $z;
}
/**
* zCode is a string that is the action associated with a rule. Expand
* the symbols in this string so that the refer to elements of the parser
* stack.
*/
function translate_code(PHP_ParserGenerator_Rule $rp)
{
$lhsused = 0; /* True if the LHS element has been used */
$used = array(); /* True for each RHS element which is used */
for($i = 0; $i < $rp->nrhs; $i++) {
$used[$i] = 0;
}
$this->append_str('', 0);
for ($i = 0; $i < strlen($rp->code); $i++) {
$cp = $rp->code[$i];
if (preg_match('/[A-Za-z]/', $cp) &&
($i === 0 || (!preg_match('/[A-Za-z0-9_]/', $rp->code[$i - 1])))) {
//*xp = 0;
// previous line is in essence a temporary substr, so
// we will simulate it
$test = substr($rp->code, $i);
preg_match('/[A-Za-z0-9_]+/', $test, $matches);
$tempcp = $matches[0];
$j = strlen($tempcp) + $i;
if ($rp->lhsalias && $tempcp == $rp->lhsalias) {
$this->append_str("\$this->_retvalue", 0);
$cp = $rp->code[$j];
$i = $j;
$lhsused = 1;
} else {
for ($ii = 0; $ii < $rp->nrhs; $ii++) {
if ($rp->rhsalias[$ii] && $tempcp == $rp->rhsalias[$ii]) {
if ($ii !== 0 && $rp->code[$ii - 1] == '@') {
/* If the argument is of the form @X then substitute
** the token number of X, not the value of X */
$this->append_str("\$this->yystack[\$this->yyidx + " .
($ii - $rp->nrhs + 1) . "]->major", -1);
} else {
$sp = $rp->rhs[$ii];
if ($sp->type == PHP_ParserGenerator_Symbol::MULTITERMINAL) {
$dtnum = $sp->subsym[0]->dtnum;
} else {
$dtnum = $sp->dtnum;
}
$this->append_str("\$this->yystack[\$this->yyidx + " .
($ii - $rp->nrhs + 1) . "]->minor", 0);
}
$cp = $rp->code[$j];
$i = $j;
$used[$ii] = 1;
break;
}
}
}
}
$this->append_str($cp, 1);
} /* End loop */
/* Check to make sure the LHS has been used */
if ($rp->lhsalias && !$lhsused) {
PHP_ParserGenerator::ErrorMsg($this->filename, $rp->ruleline,
"Label \"%s\" for \"%s(%s)\" is never used.",
$rp->lhsalias, $rp->lhs->name, $rp->lhsalias);
$this->errorcnt++;
}
/* Generate destructor code for RHS symbols which are not used in the
** reduce code */
for($i = 0; $i < $rp->nrhs; $i++) {
if ($rp->rhsalias[$i] && !isset($used[$i])) {
PHP_ParserGenerator::ErrorMsg($this->filename, $rp->ruleline,
"Label %s for \"%s(%s)\" is never used.",
$rp->rhsalias[$i], $rp->rhs[$i]->name, $rp->rhsalias[$i]);
$this->errorcnt++;
} elseif ($rp->rhsalias[$i] == 0) {
if ($rp->rhs[$i]->type == PHP_ParserGenerator_Symbol::TERMINAL) {
$hasdestructor = $this->tokendest != 0;
}else{
$hasdestructor = $this->vardest !== 0 || $rp->rhs[$i]->destructor !== 0;
}
if ($hasdestructor) {
$this->append_str(" \$this->yy_destructor(" .
($rp->rhs[$i]->index) . ", \$this->yystack[\$this->yyidx + " .
($i - $rp->nrhs + 1) . "]->minor);\n", 0);
} else {
/* No destructor defined for this term */
}
}
}
$cp = $this->append_str('', 0);
$rp->code = $cp;
}
/**
* The following routine emits code for the destructor for the
* symbol sp
*/
function emit_destructor_code($out, PHP_ParserGenerator_Symbol $sp, &$lineno)
// FILE *out;
// struct symbol *sp;
// struct lemon *lemp;
// int *lineno;
{
$cp = 0;
$linecnt = 0;
if ($sp->type == PHP_ParserGenerator_Symbol::TERMINAL) {
$cp = $this->tokendest;
if ($cp === 0) {
return;
}
$this->tplt_linedir($out, $this->tokendestln, $this->filename);
fwrite($out, "{");
} elseif ($sp->destructor) {
$cp = $sp->destructor;
$this->tplt_linedir($out, $sp->destructorln, $this->filename);
fwrite($out, "{");
} elseif ($this->vardest) {
$cp = $this->vardest;
if ($cp === 0) {
return;
}
$this->tplt_linedir($out, $this->vardestln, $this->filename);
fwrite($out, "{");
} else {
throw new Exception('emit_destructor'); /* Cannot happen */
}
for ($i = 0; $i < strlen($cp); $i++) {
if ($cp[$i]=='$' && $cp[$i + 1]=='$' ) {
fprintf($out, "(yypminor->yy%d)", $sp->dtnum);
$i++;
continue;
}
if ($cp[$i] == "\n") {
$linecnt++;
}
fwrite($out, $cp[$i]);
}
$lineno += 3 + $linecnt;
fwrite($out, "}\n");
$this->tplt_linedir($out, $lineno, $this->outname);
}
/**
* Compare to axset structures for sorting purposes
*/
static function axset_compare($a, $b)
{
return $b['nAction'] - $a['nAction'];
}
}