!empty($v) && !is_numeric($v[0]));
    }
    public function __construct(string $expression = "", array $input_vars = [])
    {
        $this->setExpression($expression);
        $this->setInputVars(($input_vars === []) ? self::collectVariables($this->expression) : $input_vars);
    }
    public function getTruthTable(): string
    {
        $N = count($this->input_vars);
        if ($N == 0) {
            return "";
        }
        $M = pow(2, $N);
        $vars = [];
        foreach ($this->input_vars as $var) {
            $vars[$var] = 0;
        }
        $expression = self::adaptToEL($this->getExpression("verilog_logic"));
//        printf("Cooked: %s\n", $cooked_form);
        $tt = [];
        for ($i = 0; $i < $M; $i++) {
            for ($k = 0; $k < $N; $k++) {
                $vars[$this->input_vars[$k]] = (($i >> ($N - $k - 1)) & 1) === 1;
//                printf("%d ", $vars[$this->input_vars[$k]]);
            }
            $out = self::$EXP_LANG->evaluate($expression, $vars);
            $out_str = $out ? "1" : "0";
//            printf("%d\n", $out);
            $tt[] = $out_str;
        }
        return join("", $tt);
    }
    public static function genTerm(array $vars, int $ftn, int $tn, int $mind, int $maxd, bool $top = true, int $opindex = 1): string
    {
        $term = "";
        $m = max($ftn, random_int(1, $tn));
        if ((($maxd === 0) || ($m === 1)) && ($ftn === 0)) {
            $neg = random_int(0, 1) === 1;
            $var = $vars[array_rand($vars, 1)];
            $term = ($neg ? "~" : "") . $var;
        } else {
            $depth = random_int(0, max(0, $maxd - 1));
            $verilog_ops = [" & ", " | "];
            $verilog_op = $verilog_ops[$opindex];
            $term = !$top ? "(" : "";
            $nextopindex = ($opindex === 0) ? 1 : 0;
            for ($i = 0; $i < $m; $i++) {
                $subterm = self::genTerm($vars, (($mind - 1) > 0) ? $ftn : 0, $tn, $mind - 1, $depth, false, $nextopindex);
                $term .= $subterm;
                if ($i < $m - 1) {
                    $term .= $verilog_op;
                }
            }
            $term .= !$top ? ")" : "";
        }
        return $term;
    }
    public static function genRandom(array $input_vars, int $min_depth = 2, int $max_depth = 3): LogicFunction
    {
        $term = self::genTerm($input_vars, count($input_vars), count($input_vars), $min_depth, $max_depth);
        return new LogicFunction($term, $input_vars);
    }
    public static function genRandomDNF($input_vars): LogicFunction
    {
        $N = count($input_vars);
        $states = pow(2, $N);
        $verilog_term = "";
        for ($i = 0; $i < $states; $i++) {
            $inside = "";
            $omit = random_int(0, 1); // omit the term or not?
            if (!$omit) {
                for ($j = 0; $j < $N; $j++) {
                    $neg = !($i & (1 << $j)); // is it an inverted variable?
                    $term = $input_vars[$j];
                    if ($neg) {
                        $inside .= "~" . $term;
                    } else {
                        $inside .= $term;
                    }
                    if ($j < ($N - 1)) {
                        $inside .= " & ";
                    }
                }
            }
            if ($inside !== "") {
                $verilog_term .= "(";
                $verilog_term .= $inside;
                $verilog_term .= ")";
                if (($i < ($states - 1)) && !$omit) {
                    $verilog_term .= " | ";
                }
            }
        }
        $verilog_term = rtrim($verilog_term, "| ");
        return new LogicFunction($verilog_term, $input_vars);
    }
    public function setExpression(string $expression): void
    {
        $this->expression = self::convertToVerilogBitwiseForm($expression);
    }
    public function getExpression(string $fmt = "verilog_bitwise"): string
    {
        switch (strtolower($fmt)) {
            case "verilog_logic":
                return str_replace(["&", "|", "~"], ["&&", "||", "!"], $this->expression);
            case "tex":
            {
                $tex_form = str_replace([" | ", " & ", "(", ")"], [" + ", " \\cdot ", "\\left(", "\\right)"], $this->expression);
                return preg_replace("/~([a-zA-Z0-9_])/", '\\overline{$1}', $tex_form);
            }
            default:
            case "verilog_bitwise":
                return $this->expression;
        }
    }
    public function getInputVars(): array
    {
        return $this->input_vars;
    }
    public function setInputVars(array $input_vars): void
    {
        $this->input_vars = $input_vars;
    }
    public function getNStates(): int
    {
        return pow(2, count($this->input_vars));
    }
    public function isValid(): bool
    {
        try {
            self::$EXP_LANG->lint(self::adaptToEL($this->expression), $this->input_vars);
        } catch (Exception $e) {
            return false;
        }
        return true;
    }
    public function toArray(): array
    {
        return ["expression" => $this->expression, "input_vars" => $this->input_vars];
    }
    public function jsonSerialize()
    {
        return $this->toArray();
    }
    public static function fromArray(array $a): LogicFunction
    {
        return new LogicFunction($a["expression"] ?? "", $a["input_vars"] ?? []);
    }
    // general minterm regex: ([/!~]*[a-zA-Z_][a-zA-Z0-9_]*[&]{1,2})*([/!~]*[a-zA-Z_][a-zA-Z0-9_]*)
    // specific regex: ([\/!~]*()[&]{1,2})*([\/!~]*())
    public static function isCorrectDNF(array $input_vars, string $exp): bool
    {
        $exp = trim($exp); // trim spaces
        if ($exp === "0") {
            return true;
        }
        $minterms = explode("|", $exp); // break up the expression into minterms
        $minterms = array_map(fn($mt) => trim($mt, " ()\t"), $minterms); // strip the parentheses off the minterms
        $minterms = array_map(fn($mt) => str_replace(" ", "", $mt), $minterms); // remove spaces
        $ivars = implode("|", $input_vars); // create | separated list of input vars to be used with the regular expression
        $regex = "/([\/!~]*(${ivars})[&]{1,2})*([\/!~]*(${ivars}))/"; // specific regular expression
        foreach ($minterms as $minterm) {
            if (preg_match($regex, $minterm) !== 1) { // generally try to match the minterm
                return false;
            }
            preg_match_all("/[\/!~]*(${ivars})[&]*/", $minterm, $matches); // fetch variables
            $vars = $matches[1] ?? [];
            sort($vars); // sort detected variables
            sort($input_vars); // sort input variables
            if ($vars !== $input_vars) { // ensure each variable occurs just once
                return false;
            }
        }
        return true;
    }
    public static function initStatic(): void
    {
        self::$EXP_LANG = new ExpressionLanguage();
    }
    public function toDNF(): string
    {
        $N = count($this->input_vars);
        $M = pow(2, count($this->input_vars));
        $tt = $this->getTruthTable();
        $minterms = [];
        for ($i = 0; $i < $M; $i++) {
            $r = $tt[$i];
            if ($r == "1") {
                $term = "(";
                for ($j = 0; $j < $N; $j++) {
                    $inv = (($i >> ($N - $j - 1)) & 1) ? "" : "~";
                    $term .= $inv . $this->input_vars[$j];
                    if ($j < ($N - 1)) {
                        $term .= " & ";
                    }
                }
                $term .= ")";
                $minterms[] = $term;
            }
        }
        $dnf = join(" | ", $minterms);
        if ($dnf === "") {
            $dnf = "0";
        }
        return $dnf;
    }
    public function drawNetwork(string $outvar = "f"): string
    {
        $expr = str_replace(["^"], [" xor "], $this->getExpression());
        return PythonUtils::execPy("draw_logic_network.py", [$expr, $outvar]);
    }
}
LogicFunction::initStatic();