<?php
namespace Tlf;
/**
* Used to process a string into an Asymmetrical Syntax Tree (AST)
*
*/
class Lexer {
use LexerTrait\Internals;
use LexerTrait\Cache;
Use LexerTrait\Directives;
Use LexerTrait\InstructionProcessor;
Use LexerTrait\Instructions;
use LexerTrait\MappedMethods;
public float $version = \Tlf\Lexer\Versions::_old;
/**
* set true to show debug messages
*/
public bool $debug = false;
/**
* The loop we're on. 0 means not started. 1 means we're executing the first loop. 2 means 2nd loop, etc.
*/
public int $loop_count = 0;
/**
* Whether to load from & save to the cache or not.
*/
public bool $useCache = true;
/**
* The token currently being processed
*/
public $token = null;
/**
* array of grammar class names & their file's last mtime
*/
protected $grammarListForCache = null;
/**
* the loop to stop processing on. Used for debugging, when writing a grammar.
*/
public $stop_loop = -1;
/**
* A string that can be set & checked by handlers, to determine what operations should be completed.
*/
public ?string $signal = null;
/**
* stop lexing
*/
public function abort(){
$this->stop_loop = $this->loop_count;
}
public function haltAll(){
$this->haltAll = true;
}
public function continueAll(){
$this->haltAll = false;
}
public function haltInstructions(){
$this->haltInstructions = true;
}
public function continueInstructions(){
$this->haltInstructions = false;
}
public function previous($key){
return $this->previous[$key] ?? null;
}
public function setPrevious($key, $value){
$old = $this->previous($key);
$this->previous[$key] = $value;
return $old;
}
public function appendToPrevious($key, $value){
$old = $this->previous($key);
if (is_array($this->previous[$key]??null))$this->previous[$key][] = $value;
else $this->previous[$key] = ($this->previous[$key]??'') . $value;
return $old;
}
public function unsetPrevious($key){
$prev = $this->previous($key);
unset($this->previous[$key]);
return $prev;
}
public function clearBuffer(){
$buffer = $this->buffer;
$this->buffer = '';
return $buffer;
}
public function getToken(){
return $this->token;
}
/**
* @param $grammar A grammar
* @param $name pass to override default namespace
* @param $executeOnAdd `false` to skip calling `onGrammarAdded`
*/
public function addGrammar(object $grammar, string $namespace=null, $executeOnAdd=true){
$grammar->setLexer($this);
$this->grammars[strtolower($namespace ?? $grammar->getNamespace())] = $grammar;
if ($executeOnAdd)$grammar->onGrammarAdded($this);
}
public function getGrammar(string $namespace){
return $this->grammars[strtolower($namespace)];
}
/**
* Append an ast to top of stack.
*
* @see See Internals::$head
*/
public function setHead($ast) {
$this->head[] = $ast;
}
/**
* Get top-level ast and remove it from the stack. If only one ast, then return it and leave at bottom of stack.
*
* @see Internals::$head
*
* @return Tlf\Lexer\Ast from top of stack
*/
public function popHead(): \Tlf\Lexer\Ast {
if (count($this->head)>1)
return array_pop($this->head);
return $this->head[0];
}
/**
* Get top-level AST from stack.
*
* See Internals::$head
*
* @return Tlf\Lexer\Ast from top of stack
*/
public function getHead(): \Tlf\Lexer\Ast {
return $this->head[count($this->head)-1];
}
/**
* Get bottom-level AST from stack.
*
* See Internals::$head
*/
public function rootAst(): \Tlf\Lexer\Ast {
return $this->head[0];
}
/*
* Create a 'file' ast & call 'lexAst'. Asts generated by this function are cached. Chache is invalidated when the source of the active grammars or the file being processed changes.
*
* @param $file an absolute file path
*/
public function lexFile($file): \Tlf\Lexer\Ast {
$debug = true;
if ($ast=$this->getCachedAst($file)){
echo "\nAst loaded from cache for file '$file'\n";
// exit;
return $ast;
}
if ($debug){
echo "\n\n\n\n/////////////////////////////////";
echo "\n/////////////////////////////////";
echo "\nParse File '$file'";
echo "\n/////////////////////////////////";
echo "\n/////////////////////////////////";
echo "\n\n\n\n";
}
$ast = new Lexer\Ast('file');
// $ast->set('source', file_get_contents($file));
$ast->set('ext', pathinfo($file,PATHINFO_EXTENSION));
$ast->set('name', pathinfo($file,PATHINFO_FILENAME));
$ast->set('path', $file);
// $this->setHead($ast);
$str = file_get_contents($ast->get('path'));
$ast = $this->lex($str, $ast);
$this->cacheFileAst($file, $ast);
return $ast;
}
}