Lexer.php

<?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;

    /**
     * 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;


    /**
     * 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)];
    }

    public function setHead($ast){
        $this->head[] = $ast;
    }
    public function popHead(){
        if (count($this->head)>1)
            return array_pop($this->head);

        return $this->head[0];
    }
    public function getHead(){
        return $this->head[count($this->head)-1];
    }
    public function rootAst(){
        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){
        $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;
    }

}