Rumah  >  Artikel  >  pembangunan bahagian belakang  >  PHP juga boleh melaksanakan analisis leksikal dan bahasa tersuai!

PHP juga boleh melaksanakan analisis leksikal dan bahasa tersuai!

藏色散人
藏色散人ke hadapan
2022-01-12 15:08:104737semak imbas

Terdapat keperluan dalam projek sebelumnya Kakitangan perniagaan menulis beberapa formula tersuai dalam bahasa Cina, dan kemudian kami perlu melaksanakannya di latar belakang untuk mengembalikan hasil kepada antara muka, jadi kami menulis ini. penganalisis leksikal berdasarkan mesin keadaan terhingga Ia agak mudah, saya harap ia dapat memberi inspirasi kepada orang lain.

1 Keperluan analisis

Masukkan formula Cina dan kembalikan hasilnya, seperti:

现有薪资=10000;
个税起点=3000;
当前年份=2021;
如果(当前年份=2022){
    个税起点=5000;
}
返回 (现有薪资-个税起点) * 0.2;

2. jangan fikir ia sangat cantik, dan ia tidak boleh mencapai analisis dinamik. Saya baru terfikir untuk melaksanakan analisis leksikal mudah sendiri, dan kemudian digabungkan dengan ast untuk menukar leksikon kepada kod php untuk pelaksanaan, bukankah ia menyeronokkan? Versi semasa tidak menggunakan pepohon sintaks abstrak untuk menjana kod, dan semuanya menggunakan penggabungan rentetan. [Pembelajaran yang disyorkan: Tutorial video PHP

]

3. Cara menggunakan
<?php

/**
 * Class Lexer
 * @package Sett\OaLang
 * 词法分析器
 */
class Lexer {
    // 内置关键字集合
    public $keywordList = [];
    // 内置操作符集合
    public $operatorList = [
        "+", "-", "*", "/", "=", ">", "<", "!", "(", ")", "{", "}", ",", ";"
    ];
    // 源代码
    private $input;
    // 当前的字符
    private $currChar;
    // 当前字符位置
    private $currCharPos = 0;
    // 结束符
    private $eof = "eof";
    // 当前编码
    private $currEncode  = "UTF-8";

    // 内置关键字
    public const VAR = "variable";
    public const STR = "string";
    public const KW  = "keyword";
    public const OPR = "operator";
    public const INT = "integer";
    public const NIL = "null";


    /**
     * Lexer constructor.
     * @param string $input
     */
    public function __construct(string $input) {
        $this->input    = $input;
        $this->currChar = mb_substr($this->input, $this->currCharPos, 1);
    }

    /**
     * @param array $keywordList
     */
    public function setKeywordList($keywordList) {
        $this->keywordList = $keywordList;
    }

    /**
     * @return array
     * @throws Exception
     */
    public function parseInput() {
        if ($this->input == "") {
            throw new Exception("code can not be empty");
        }
        $tokens = [];
        do {
            $token = $this->nextToken();
            if ($token["type"] != "eof") {
                $tokens[] = $token;
            }
            if ($token["type"] == self::KW) {
                $tokens[] = $this->makeToken(self::NIL, " ");
            }
        } while ($token["type"] != "eof");
        return $tokens;
    }

    /**
     * @return array
     */
    public function nextToken() {
        $this->skipBlankChar();
        $this->currChar == "" && $this->currChar = $this->eof;
        if ($this->isCnLetter()) {
            $word = $this->matchUntilNextCharIsNotCn();
            if ($this->isKeyword($word)) {
                $this->currCharPos -= 1;
                return $this->currToken(static::KW, $word);
            }
            // 不是关键字的全部归为变量
            return $this->makeToken(static::VAR, $word);
        }
        // 如果是操作符
        if ($this->isOperator()) {
            return $this->currToken(static::OPR, $this->currChar);
        }
        // 如果是数字
        if ($this->isNumber()) {
            return $this->currToken(static::INT, $this->currChar);
        }
        // 如果是字符串
        if ($str = $this->isStr()) {
            return $this->currToken(static::STR, $str);
        }
        // 如果是变量
        if ($this->isVar()) {
            $word = $this->matchVar();
            if ($this->isKeyword($word)) {
                return $this->currToken(static::KW, $word);
            }
            return $this->makeToken(static::VAR, $word);
        }
        if ($this->currChar == $this->eof) {
            return $this->currToken(&#39;eof&#39;, $this->currChar);
        }
        return $this->currToken(static::VAR, $this->currChar);
    }

    /**
     * @param string $input
     * @return string
     */
    private function matchVar(string $input = "") {
        $word = $input ?: &#39;&#39;;
        while ($this->isVar()) {
            $word .= $this->currChar;
            $this->nextChar();
        }
        return $word;
    }

    /**
     * @return bool
     * 是否为普通变量
     */
    private function isVar() {
        return $this->isCnLetter() || $this->isEnLetter();
    }


    /**
     * 跳过空白字符
     */
    private function skipBlankChar() {
        while (ord($this->currChar) == 10 ||
            ord($this->currChar) == 13 ||
            ord($this->currChar) == 32) {
            $this->nextChar();
        }
    }

    /**
     * @param string $type
     * @param $word
     * @return array
     * 记录当前token和下一个字符
     */
    private function currToken(string $type, $word) {
        $token = $this->makeToken($type, $word);
        $this->nextChar();
        return $token;
    }

    /**
     * @param string $type
     * @param string $char
     * @return array
     */
    private function makeToken(string $type, string $char) {
        return ["type" => $type, "char" => $char, "pos" => $this->currCharPos];
    }


    /**
     * @return bool
     * 判断是否是英文字符
     */
    private function isEnLetter() {
        if ($this->currChar == "" || $this->currChar == $this->eof) {
            return false;
        }
        $ord = mb_ord($this->currChar, $this->currEncode);
        if ($ord > ord(&#39;a&#39;) && $ord < ord(&#39;z&#39;)) {
            return true;
        }
        return false;
    }

    /**
     * @return false|int
     * 是否中文字符
     */
    private function isCnLetter() {
        return preg_match("/^[\x{4e00}-\x{9fa5}]+$/u", $this->currChar);
    }

    /**
     * @return bool
     * 是否为数字
     */
    private function isNumber() {
        return is_numeric($this->currChar);
    }

    /**
     * @return bool
     * 是否是字符串
     */
    private function isStr() {
        return $this->matchCompleteStr();
    }

    /**
     * @return string
     * 匹配完整字符串
     */
    private function matchCompleteStr() {
        $char = "";
        if ($this->currChar == "\"") {
            $this->nextChar();
            while ($this->currChar != "\"") {
                if ($this->currChar != "\"") {
                    $char .= $this->currChar;
                }
                $this->nextChar();
            }
            return $char;
        }
        return $char;
    }

    /**
     * @return bool
     * 是否是操作符
     */
    private function isOperator() {
        return in_array($this->currChar, $this->operatorList);
    }

    /**
     * @return string
     * 匹配中文字符
     */
    private function matchUntilNextCharIsNotCn() {
        $char = "";
        while ($this->isCnLetter()) {
            $char .= $this->currChar;
            $this->nextChar();
        }
        return $char;
    }

    /**
     * @return void 获取下一个字符
     * 获取下一个字符
     */
    private function nextChar() {
        $this->currCharPos += 1;
        $this->currChar    = mb_substr($this->input, $this->currCharPos, 1);
        if ($this->currChar == "") {
            $this->currChar = $this->eof;
        }
    }

    /**
     * @param string $input
     * @return bool
     * 是否是关键字
     */
    private function isKeyword(string $input) {
        return ($this->keywordList[$input] ?? "") != "";
    }

    public function convert(array $tokens) {
        $code = "";
        foreach ($this->lexerIterator($tokens) as $generator) {
            switch ($generator["type"]) {
                case static::KW:
                    $code .= $this->keywordList[$generator["char"]];
                    break;
                case static::VAR:
                    $code .= sprintf("$%s", $generator["char"]);
                    break;
                case static::OPR:
                    $code .= $this->replace($generator["char"]);
                    break;
                case static::INT:
                    $code .= $generator["char"];
                    break;
                case static::STR:
                    $code .= sprintf("\"%s\"", $generator["char"]);
                    break;
                default:
                    $code .= $generator["char"];
            }
        }
        return $code;
    }

    private function replace(string $char) {
        return str_replace("+", ".", $char);
    }

    /**
     * @param array $tokens
     * @return \Generator
     */
    private function lexerIterator(array $tokens) {
        foreach ($tokens as $index => $token) {
            yield $token;
        }
    }
}

Jana perkataan

require __DIR__ . "/vendor/autoload.php";
// 定义一段代码
$code = <<<EOF
姓名="腕豪";
问候="你好啊";
地址=(1+2) * 3;
如果(地址 > 3){
    地址=1;
}否则{
    地址="艾欧尼亚"
}
说话 = ("我"+"爱")+"你";
返回 姓名+年龄;
EOF;
$lexer = new Lexer($code);
// 自定义你的关键字
$kwMap = [
    "如果" => "if", "否则" => "else", "返回" => "return", "否则如果" => "elseif"
];
$lexer->setKeywordList($kwMap);
// 这里是生成的词
$tokens = $lexer->parseInput();
// 将生成的词转成php,当然你也可以尝试用php-parse转ast再转成php,这里只是简单的拼接
var_dump($lexer->convert($tokens));

Output:

[{
    "type": "variable",
    "char": "姓名",
    "pos": 2}, {
    "type": "operator",
    "char": "=",
    "pos": 2}, {
    "type": "string",
    "char": "腕豪",
    "pos": 7}, {
    "type": "operator",
    "char": ";",
    "pos": 8}, {
    "type": "variable",
    "char": "问候",
    "pos": 13}, {
    "type": "operator",
    "char": "=",
    "pos": 13}, {
    "typ e": "string",
    "char": "你好啊",
    "pos": 17}, {
    "type": "operator",
    "char": ";",
    "pos": 18}, {
    "type": "variable",
    "char": "地址",
    "pos": 23}, {
    "type": "operator",
    "char": "=",
    "pos": 23}, {
    "type": "operator",
    "char": "(",
    "pos": 24}, {
    "type": "integer",
    "char": "1",
    "pos": 25}, {
    "type": "operator",
    "char": " +",
    "pos": 26}, {
    "type": "integer",
    "char": "2",
    "pos": 27}, {
    "type": "operator",
    "char": ")",
    "pos": 28}, {
    "type": "operator",
    "char": "*",
    "pos": 30}, {
    "type": "integer",
    "char": "3",
    "pos": 32}, {
    "type": "operator",
    "char": ";",
    "pos": 33}, {
    "type": "keyword",
    "char": "如果",
    "pos": 37}, {
    "type": "nul l",
    "char": " ",
    "pos": 38}, {
    "type": "operator",
    "char": "(",
    "pos": 38}, {
    "type": "variable",
    "char": "地址",
    "pos": 41}, {
    "type": "operator",
    "char": ">",
    "pos": 42}, {
    "type": "integer",
    "char": "3",
    "pos": 44}, {
    "type": "operator",
    "char": ")",
    "pos": 45}, {
    "type": "operator",
    "char": "{",
    "pos": 46}, {
    "type": "variable",
    "char": "地址",
    "pos": 55}, {
    "type": "operator",
    "char": "=",
    "pos": 55}, {
    "type": "integer",
    "char": "1",
    "pos": 56}, {
    "type": "operator",
    "char": ";",
    "pos": 57}, {
    "type": "operator",
    "char": "}",
    "pos": 60}, {
    "type": "keyword",
    "char": "否则",
    "pos": 62}, {
    "type": "null",
    "char ": " ",
    "pos": 63}, {
    "type": "operator",
    "char": "{",
    "pos": 63}, {
    "type": "variable",
    "char": "地址",
    "pos": 72}, {
    "type": "operator",
    "char": "=",
    "pos": 72}, {
    "type": "string",
    "char": "艾欧尼亚",
    "pos": 78}, {
    "type": "operator",
    "char": ";",
    "pos": 79}, {
    "type": "operator",
    "char": "}",
    "pos": 82}, {
    "type": "variable",
    "char": "说话",
    "pos": 87}, {
    "type": "operator",
    "char": "=",
    "pos": 88}, {
    "type": "operator",
    "char": "(",
    "pos": 90}, {
    "type": "string",
    "char": "我",
    "pos": 93}, {
    "type": "operator",
    "char": "+",
    "pos": 94}, {
    "type": "string",
    "char": "爱",
    "pos": 97}, {
    "type": "operator",
    "char": ")",
    "pos": 98}, {
    "type": "operator",
    "char": "+",
    "pos": 99}, {
    "type": "string",
    "char": "你",
    "pos": 102}, {
    "type": "operator",
    "char": ";",
    "pos": 103}, {
    "type": "keyword",
    "char": "返回",
    "pos": 107}, {
    "type": "null",
    "char": " ",
    "pos": 108}, {
    "type": "variable",
    "char": "姓名",
    "pos": 111}, {
    "typ e": "operator",
    "char": "+",
    "pos": 111}, {
    "type": "variable",
    "char": "年龄",
    "pos": 114}, {
    "type": "operator",
    "char": ";",
    "pos": 114}]

Bolehkah ia dilaksanakan? Sudah tentu boleh. Masih terdapat beberapa pepijat kecil yang saya tidak mahu ubah.

$姓名="腕豪";$问候="你好啊";$地址=(1.2)*3;if ($地址>3){$地址=1;}else {$地址="艾欧尼亚";}$说话=("我"."爱")."你";return $姓名.$年龄;

4 Senario Penggunaan Apa, sesetengah orang kata ia tidak berguna? Sistem oa akan sentiasa berguna.

Atas ialah kandungan terperinci PHP juga boleh melaksanakan analisis leksikal dan bahasa tersuai!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:learnku.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam