<?php

/**
 *  @author          Tassos.gr <info@tassos.gr>
 *  @link            https://www.tassos.gr
 *  @copyright       Copyright © 2024 Tassos All Rights Reserved
 *  @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/

namespace Tassos\Framework\Parser;

defined('_JEXEC') or die;

use Tassos\Framework\Parser\Lexer;
use Tassos\Framework\Parser\RingBuffer;

/**
 *  Parser base class
 *  LL(k) recursive-decent parser with backtracking support
 */
abstract class Parser
{
    /**
     *  Lexer instance (feeds the parser with tokens)
     *  
     *  @var Tassos\Framework\Parser\Lexer
     */
    protected $input = null;

    /**
     *  Ring buffer of the next k tokens
     *  from the input stream
     *
     *  @var RingBuffer
     */
    protected $lookahead = null;

    /**
     *  k: Number of lookahead tokens
     *
     *  @var int
     */
    protected $k;

    /**
     *  Array(stack) containing  the current 
     *  contents of the lookahead buffer when 
     *  marking the position of the stream
     * 
     *  @var array
     */
    protected $lookahead_history = null;

    /**
     *  Lexer constructor
     *
     *  @param Lexer   $input
     *  @param integer $k, number of lookahead tokens
     */
    public function __construct(Lexer $input, $k = 1)
    {
        if (!is_integer($k) || ($k < 1))
        {
            throw new \InvalidArgumentException('Parser: $k must be greater than 0!');
        }
        $this->k                 = $k;
        $this->input             = $input;
        $this->lookahead_history = [];

        // initialize lookahead buffer
        $this->resetBuffer();
    }

    /**
     *  Checks the type of the next token.
     *  Advances the position in the token stream.
     *  
     *  @param string $type
     *  @return void
     * 
     *  @throws Exception
     */
    public function match($type)
    {
        if ($this->lookahead[0]->type === $type)
        {
            $this->consume();
            return;
        }

        throw new Exceptions\SyntaxErrorException('Expecting token ' . $type . ', found ' . $this->lookahead[0]);
    }

    /**
     *  Retrieves the next token from the input stream
     *  and add it to the buffer.
     *
     *  @return void
     */
    public function consume()
    {
        $this->lookahead[] = $this->input->nextToken();
    }

    /**
     *  Marks the position in the token stream
     * 
     *  @return void
     */
    public function mark()
    {
        array_push($this->lookahead_history, $this->lookahead);
        $this->input->mark();
    }

    /**
     *  Reset to a previously marked position
     *  in the token stream
     * 
     *  @return void
     */
    public function reset()
    {
        $this->input->reset();

        // reset lookahead buffer if not marked
        if (empty($this->lookahead_history))
        {
            $this->resetBuffer();
        }
        // normal reset
        else
        {
            $this->lookahead = array_pop($this->lookahead_history);
        }
    }

    /**
     *  Resets and refills the lookahead buffer starting
     *  from the current position in the token stream
     *
     *  @return void
     */
    protected function resetBuffer()
    {
        $this->lookahead = new RingBuffer($this->k);
        for ($i=0; $i < $this->k; $i++)
        {
            $this->consume();
        }
    }
}
