<?php
namespace Tlf\LexerTrait;
/**
* Contains the internals for lexing.
*/
trait Internals {
/**
* That last loop on which 'then' was executed.
* Used to control directive layer stacking.
*/
public int $last_then_loop = -1;
/**
* All the grammars that have been added
*/
protected $grammars = [];
/**
* Stack of ast objects. Top one is passed to lex functions
*/
protected array $head = [];
/**
* Calling $lexer->previous('docblock') will get the previous docblock
* $lexer->setPrevious('docblock', value) will set the previous docblock
* ['nameOfPrevItem'
*/
public $previous = ['docblock'=>null];
/**
* Processes an ast, setting it as the root (first head), executing grammar onLexerStart & onLexerEnd
* @param $str a string to lexify
* @param $ast an ast to use as root. An 'str' ast will be created if none is supplied.
*/
public function lex($str, $ast=null){
if ($ast===null){
$ast = new \Tlf\Lexer\Ast('str');
$ast->set('src', $str);
}
$debug = $this->debug;
$this->setHead($ast);
$token = new \Tlf\Lexer\Token($str);
$this->token = $token;
foreach ($this->grammars as $grammar){
$grammar->lexer_started($this, $ast, $token);
$grammar->onLexerStart($this,$ast,$token);
}
$this->loop_count = 0;
// This while loop could be put into a single function.
// If I add threading, it might become necessary.
while($token = $token->next()){
++$this->loop_count;
$list = &$this->topDirectivesList();
if ($debug){
$this->debug_loop($token,$list);
}
$toProcess = $list['unstarted'];
$instructionSets = ['start'];
if (count($list['started'])>0){
$toProcess = $list['started'];
$instructionSets = ['match', 'stop'];
}
$this->haltAll = false;
foreach ($toProcess as $directive){
foreach ($instructionSets as $instructionSetName){
$this->haltInstructions = false;
if ($this->haltAll)break 2;
$this->processInstructions($directive, $instructionSetName, $list);
}
}
$this->haltAll = false;
if ($this->stop_loop==$this->loop_count){
echo "\n\n\n\n\n\n";
print_r($this->head[0]->getTree());
echo "\n----Stop_loop count was reached!----\n\n";
echo "\n\n";
exit;
}
}
foreach ($this->grammars as $grammar){
$grammar->onLexerEnd($this, $ast, $lastToken??null);
}
return $this->rootAst();
// return $ast;
}
/**
* Check if a target passes
* @return false or a $matches array, like you get from preg_match
*/
protected function doesATargetPass($directive, $matchTargetList, string $toMatch){
// $instructions = $directive->$instructionSetName ?? false;
// if ($instructions == false)return false;
// $matchTargetList = $instructions['match'] ?? [];
// $toMatch = $this->token->buffer();
// echo "\n\n-++++--\n";
$matches = [$toMatch];
foreach ($matchTargetList as $target){
$target = $this->fillInTarget($target, $directive);
if (
$target===true
||substr($target,0,1)=='/'&&preg_match($target, $toMatch, $matches)
||substr($toMatch,-strlen($target))==$target&&$matches=[$target, $target]
){
return $matches;
}
}
return false;
}
/**
* Get a corrected target regex/string/bool
*
* @param $target a regex/string/bool value. Regex/string in form of '/a$1c/' or `['/a',1,'c/']` is valid
* @return string or bool, corrected target with any placeholders replaced by matches
*/
protected function fillInTarget($target, $directive){
if (is_bool($target))return $target;
if (is_array($target)){
$out = '';
foreach ($target as $value){
if (is_int($value))$value = $previousMatches[$value];
$out .= $value;
}
return $out;
}
if ($directive->_fillReg??false)return $target;
$reg = '/\$[0-9]/';
$newTarget = preg_replace_callback($reg,
function($matches) use ($directive){
$index = (int)substr($matches[0],1);
$replacement = $directive->_matches[$index];
$replacement = preg_quote($replacement, '/');
return $replacement;
},
$target
);
return $newTarget;
}
/**
* Print debug information for the current loop.
*/
protected function debug_loop($token, $list){
if ($this->debug){
$newChar=str_replace("\n",'\\n',substr($token->buffer(),-1));
$bufferEnd=substr($token->buffer(),-20);
$bufferEnd=str_replace("\n",'\\n',$bufferEnd);
echo "\n\n\033[45m----- +$newChar+ Loop ".$this->loop_count." `$bufferEnd` -----\033[0m\n";
echo "Signal: ".$this->signal;
echo "\nToken: ".
"\n New Char: ".$newChar
."\n Buffer Len: ".strlen($token->buffer())
."\n Buffer End: ".$bufferEnd
."\n Next Chars: ".substr($token->remainder(),0,5)
."\n Chars Remaining: ".strlen($token->remainder());
echo "\nAst Stack has ".count($this->head)." entries.";
foreach ($this->head as $debug_ast_index=>$debug_ast){
$keys = $debug_ast->getAll();
unset($keys['type']);
$keys = array_keys($keys);
echo "\n lvl${debug_ast_index}: "
.'('.$debug_ast->type.')'
.' with entries: '.implode(', ', $keys);
}
echo "\nDirective Stack has ".count($this->directiveStack)." layers.";
foreach ($this->directiveStack as $index=>$layer){
echo "\n lvl${index}: "
." (unstarted) "
.implode(", ",array_keys($layer['unstarted']));
echo "\n (started) "
.implode(", ",array_keys($layer['started']));
}
if (($listCount=count($list['started']))>0){
echo "\nCheck 'match' and 'stop' on ${listCount} directives";
} else {
$listCount = count($list['unstarted']);
echo "\nCheck 'start' on $listCount directives";
}
}
}
}