Commit 1cba35e4 authored by Laurent Lecluse's avatar Laurent Lecluse
Browse files

Nouveau système d'interprétation de formule

parent d6411554
Loading
Loading
Loading
Loading
Loading

src/Calc/Formule.php

0 → 100644
+364 −0
Original line number Diff line number Diff line
<?php

namespace Unicaen\OpenDocument\Calc;

class Formule
{
    private string $formule;

    private array  $terms = [];

    private array  $expr  = [];



    public function __construct(string $formule)
    {
        $this->formule = $formule;
    }



    private function t(int $index): array
    {
        if (array_key_exists($index, $this->terms)) {
            return $this->terms[$index];
        } else {
            return ['type' => 'end', 'v' => null];
        }
    }



    private function tw(array $term, int $index, int $length = 1)
    {
        $this->terms[$index] = $term;
        if ($length > 1) {
            for ($i = 1; $i < $length; $i++) {
                unset($this->terms[$index + $i]);
            }
            $this->terms = array_values($this->terms);
        }
    }



    public function analyse()
    {
        $this->terms = [];
        for ($i = 4; $i < strlen($this->formule); $i++) {
            $this->terms[] = [
                'type' => null,
                'v'    => $this->formule[$i],
            ];
        }

        $this->lexing();
        $this->grammar();
    }



    public function displayTerms()
    {
        echo '<div style="clear: both"><code>' . htmlentities($this->formule) . '</code></div>';
        echo '<div>';
        foreach ($this->terms as $index => $term) {
            $this->displayTerm($index, $term);
        }
        echo '</div>';
    }



    private function displayTerm(int $index, array $term)
    {
        $type = $term['type'];
        unset($term['type']);

        ?>
        <div style="width:120px;float:left;margin:2px">
            <div class="panel panel-default">
                <div class="panel-heading"><b><?= $index ?></b>&nbsp;<?= $type ?: '<span style="color:red">IND</span>' ?></div>
                <table class="table table-bordered table-condensed table-extra-condensed">
                    <?php foreach ($term as $t => $v): ?>
                        <tr>
                            <th><?= $t ?></th>
                            <td><?= htmlentities($v) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </table>
            </div>
        </div>
        <?php
    }



    private function lexing()
    {
        $lexers = [
            'lexString',
            'lexVarRange',
            'lexOpsSeps',
            'lexOthers',
        ];

        foreach ($lexers as $lexer) {
            $i = 0;
            while (($t = $this->t($i)) && $t['type'] !== 'end') {
                if ($t['type'] === null) {
                    $this->{$lexer}($i);
                }
                $i++;
            }
        }
    }



    private function lexString(int $i)
    {
        $t = $this->t($i);
        if ($t['v'] !== '"') {
            return;
        }

        $string = '';
        $length = 2;

        $c = $i;
        do {
            $c++;
            $t = $this->t($c);
            if ($t['type'] === null) {
                if ($t['v'] !== '"') {
                    $string .= $t['v'];
                    $length++;
                } else {
                    $next = $this->t($c + 1);
                    if ($next['type'] === null && $next['v'] === '"') {
                        $string .= '"';
                        $length += 2;
                        $c++;
                        $t['v'] = '';
                    }
                }
            }
        } while (!($t['type'] != null || $t['v'] === '"'));

        $this->tw([
            'type'    => 'string',
            'content' => $string,
        ], $i, $length);
    }



    private function lexVarRange(int $i)
    {
        $t = $this->t($i);
        if ($t['v'] !== '[') {
            return;
        }

        $val    = '';
        $length = 1;

        $c = $i;
        do {
            $c++;
            $t = $this->t($c);
            if ($t['v'] != ']') {
                $val .= $t['v'];
            }
            $length++;
        } while ($t['type'] === null && $t['v'] !== ']');


        if ($val[0] !== '.') {
            return;
        }

        $val = substr($val, 1);

        if (false !== strpos($val, ':')) {
            $range = explode(':', $val);
            $term  = [
                'type'  => 'range',
                'begin' => $range[0],
                'end'   => substr($range[1], 1),
            ];
        } else {
            $term = [
                'type' => 'cell',
                'name' => $val,
            ];
        }

        $this->tw($term, $i, $length);
    }



    private function lexOpsSeps(int $i)
    {
        $t = $this->t($i);

        if ($t['v'] === '<') {
            $next = $this->t($i + 1);
            if ($next['type'] === null && $next['v'] === '>') {
                $this->tw(['type' => 'op', 'name' => '<>'], $i, 2);
            }
        }

        $ops = ['<', '>', '-', '+', '*', '/', '='];
        if (in_array($t['v'], $ops)) {
            $this->tw(['type' => 'op', 'name' => $t['v']], $i);
        }

        $seps = ['(', ')', ';'];
        if (in_array($t['v'], $seps)) {
            $this->tw(['type' => 'sep', 'name' => $t['v']], $i);
        }
    }



    private function lexOthers(int $i)
    {
        $c      = $i;
        $t      = $this->t($c);
        $v      = '';
        $length = 0;
        $type   = null;
        while ($t['type'] === null && $t['type'] !== 'end') {

            $v .= $t['v'];
            $length++;
            $c++;
            $t = $this->t($c);
        }

        /* Détection de nombres */
        if ($v === '0') {
            $this->tw(['type' => 'number', 'value' => 0], $i);

            return;
        } else {
            $vt = str_replace([',', ' '], ['.', ''], $v);
            $vf = (float)$vt;
            if ((string)$vf === $vt) {
                $this->tw(['type' => 'number', 'value' => $vf], $i);

                return;
            }
        }

        $this->tw(['type' => 'variable', 'name' => $v], $i, $length);

        /* Détection de fonctions */
        $next = $this->t($i + 1);
        if ($next['type'] === 'sep' && $next['name'] === '(') {
            $this->tw(['type' => 'function', 'name' => $v], $i);
        }
    }



    private function grammar()
    {
        $expr = &$this->terms;


        // on met la structure en relief
        $level = 1;
        foreach ($expr as $i => $term) {
            if ($term['type'] === 'sep' && $term['name'] === ')') {
                $level--;
            }
            if ($term['type'] === 'sep' && $term['name'] === ';') {
                $expr[$i]['level'] = $level - 1;
            } else {
                $expr[$i]['level'] = $level;
            }

            if ($term['type'] === 'sep' && $term['name'] === '(') {
                $level++;
            }
        }

        $this->expr = $this->terms;

        $this->analyseExpr($this->expr, 0);
    }



    private function searchExpr(array &$expr, int $i, array $filters): ?int
    {
        foreach( $expr as $c => $term ){
            if ($c >= $i) {
                $match = true;
                foreach ($filters as $name => $value) {
                    if ($term[$name] !== $value) {
                        $match = false;
                    }
                    if ($match) return $c;
                }
            }
        }
        return null;
    }



    private function analyseExpr(array &$expr, int $i)
    {
        while( ($i = $this->searchExpr($expr, $i, ['type' => 'function'])) !== null ){
            $this->buildFunction($expr, $i);
            $i++;
        }
    }



    private function buildFunction(array &$expr, int $i)
    {
        $level       = $expr[$i]['level'];
        $exprs       = [];
        $currentExpr = 0;
        $end = count($expr);
        
        unset($expr[$i+1]); // on supprime la parenthèse ouvrante
        for ($c = $i + 2; $c < $end; $c++) {
            $t = $expr[$c];
            unset($expr[$c]);
            if ($t['type'] === 'sep' && $t['name'] === ')' && $t['level'] === $level) {
                // fin de la fonction
                break;
            }elseif ($t['type'] === 'sep' && $t['name'] === ';' && $t['level'] === $level){
                $currentExpr++;
                
            }else{
                $exprs[$currentExpr][] = $t;
            }
            //  if ()
        }

        for($e=0;$e<=$currentExpr;$e++){
            $this->analyseExpr($exprs[$e], 0);
        }
        $expr[$i]['exprs'] = $exprs;
        $expr = array_values($expr);
    }



    public function displayExprs()
    {
        echo '<div style="clear:both">';
        phpDump($this->expr);
        echo '</div>';
    }
}
 No newline at end of file