<?php
/*
 * Copyright (c) 2003-2022 Willem Dijkstra
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

require_once(__DIR__ . '/class_vars.inc');
require_once(__DIR__ . '/tools.inc');
require_once(__DIR__ . '/setup.inc');

class Lexer {
    public $eof = 0;
    public $filename = '';
    public $lineno = 0;
    public $lineitems;
    public $line = [];
    public $buffer = [];
    public $token = '';
    public $unget = [];
    public $keywordchrs = '/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.';

    function __construct()
    {
    }

    function is_eof() {
        return $this->eof;
    }

    function load($filename = '') {
        global $symon;

        if ($filename == '') {
            runtime_error('layout parser: no file to load');
        }
        $filename = normalise_filename($filename);
        $filename = $symon['layout_dir'].'/'.normalise_filename($filename).'.layout';
        $this->filename = $filename;
        $buffer = load($filename);
        $this->buffer = preg_split("/\n/", (string) $buffer);
    }

    function next_token() {
        global $symon;

        $token = $this->_next_token();

        if (isset($symon['lexer_debug'])) {
            $this->_display();
        }

        return $token;
    }

    function parse_error($errorstring, $lineno = 0) {
        if ($lineno == 0) {
            $lineno = $this->lineno;
        }

        runtime_error("parse error: $this->filename : $lineno : '$this->token' : $errorstring");
    }

    function parse_semicolon() {
        if ($this->eof) {
            $this->parse_error('Expected ;');
        }
        $token = $this->next_token();
        if ($token != ';') {
            $this->parse_error('Expected ;');
        }
    }

    function unget($token) {
        $items = preg_split('//', (string) $token, -1, PREG_SPLIT_NO_EMPTY);
        foreach ($items as $k => $item) {
            $this->_unget_chr($item);
        }
    }

    function _get_chr() {
        $item = $this->_get_chr_and_nl();
        if ($item == "\n") {
            $item = ' ';
        }
        return $item;
    }

    function _get_chr_and_nl() {
        if ($this->eof) {
            runtime_error('internal: trying to get_chr while eof has been signalled');
        }
        if ((is_countable($this->unget) ? count($this->unget) : 0) > 0) {
            return array_shift($this->unget);
        }
        $item = '';
        while ($item == '') {
            if ((is_countable($this->lineitems) ? count($this->lineitems) : 0) == 0) {
                $this->_get_next_line();
                if ($this->eof) {
                    return;
                }
                return "\n";
            }
            $item = array_shift($this->lineitems);
        }
        return $item;
    }

    function _get_next_line() {
        if ($this->eof) {
            runtime_error('internal: trying to get_next_line while eof has been signalled');
        }
        if ((is_countable($this->buffer) ? count($this->buffer) : 0) == 0) {
            $this->eof = 1;
            return;
        }
        $this->line = array_shift($this->buffer);
        $this->lineno += 1;
        $this->lineitems = preg_split('//', (string) $this->line, -1, PREG_SPLIT_NO_EMPTY);
    }

    function _next_token() {
        if ($this->eof) {
            runtime_error('internal: trying next_token while eof has been signalled');
        }

        $this->token = '';
        $ch = $this->_get_chr();

        while ($this->eof || str_contains(" \t\r\n#", (string) $ch)) {
            if ($this->eof) {
                return;
            }
            if ($ch == '#') {
                $this->_get_next_line();
            }
            $ch = $this->_get_chr();
        }

        if ($this->eof) {
            $this->token = '';
            return $this->token;
        }

        if ($ch == '"') {
            $this->token = $this->_get_quoted_string('"');
            return $this->token;
        }
        if ($ch == "'") {
            $this->token = $this->_get_quoted_string("'");
            return $this->token;
        }
        if (str_contains((string) $this->keywordchrs, (string) $ch)) {
            $this->token = '';
            while (str_contains((string) $this->keywordchrs, (string) $ch) || $this->eof) {
                $this->token .= $ch;
                $ch = $this->_get_chr();
            }
            if (!$this->eof) {
                $this->_unget_chr($ch);
            }
            return $this->token;
        }
        $this->token = $ch;
        return $this->token;
    }

    function _get_quoted_string($quote) {

        $start_lineno = $this->lineno;
        $this->token = '';
        $delimited = 0;

        $ch = $this->_get_chr_and_nl();
        while ($ch != $quote) {
            if ($this->eof) {
                $this->parse_error("starting $quote without closing $quote", $start_lineno);
                return;
            }
            if ($ch == "\\" && $quote == '"') {
                $ch = $this->_get_chr_and_nl();
            }
            $this->token .= $ch;
            $ch = $this->_get_chr_and_nl();
        }
        return $this->token;
    }

    function _unget_chr($ch) {
        $this->unget[] = $ch;
    }

    function _display() {
        print "<pre>\xa -- token = $this->token";
        print "\xa lexing $this->filename:$this->lineno";
        print "\xa line = $this->line";
        print "\xa items = ";
        if ((is_countable($this->lineitems) ? count($this->lineitems) : 0) > 0) {
            foreach ($this->lineitems as $k => $item) {
                print $item. '|';
            }
        }
        print "\xa unget = ";
        if ((is_countable($this->unget) ? count($this->unget) : 0) > 0) {
            foreach ($this->unget as $item) {
                print $item. '|';
            }
        }
        print "\xa eof = $this->eof </pre>";
    }

    function _test() {
        global $symon;

        $this->load('hifn_test.layout');
        while(!$this->eof) {
            $token = $this->next_token();
        }
    }
}
?>
