Internals.php

<?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 "Token: ".
                "\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";
            }

        }

    }

}