>  기사  >  백엔드 개발  >  PHP는 어휘 분석 및 사용자 정의 언어도 구현할 수 있습니다!

PHP는 어휘 분석 및 사용자 정의 언어도 구현할 수 있습니다!

藏色散人
藏色散人앞으로
2022-01-12 15:08:104700검색

이전 프로젝트에는 비즈니스 직원이 중국어로 몇 가지 사용자 정의 수식을 작성한 후 결과를 인터페이스로 반환하기 위해 백그라운드에서 이를 실행해야 한다는 요구 사항이 있었습니다. 그래서 유한 기반의 어휘 분석기를 작성했습니다. 상태 머신은 비교적 간단하며 다른 사람들에게 영감을 줄 수 있기를 바랍니다.

1. 요구 사항 분석

중국어 수식을 입력하고 다음과 같은 결과를 반환합니다.

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

2. 요구 사항 구현

원래 아이디어는 문자열 대체를 사용하여 중국어를 바꾸는 것입니다. 키워드를 PHP 키워드와 함께 사용하고 eval을 호출하여 실행하는 것은 가능하지만 항상 아름답지 않고 동적 구문 분석을 수행할 수 없다고 생각합니다. 그냥 제가 직접 간단한 어휘 분석을 구현해보고, ast와 결합해서 어휘를 PHP 코드로 변환해서 실행해볼까 하는 생각이 들었습니다. 재미있지 않을까요? 현재 버전은 코드를 생성하기 위해 추상 구문 트리를 사용하지 않으며 모두 문자열 연결을 사용합니다. [추천 학습: PHP 비디오 튜토리얼]

<?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;
        }
    }
}

3.

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));

를 사용하여 단어를 생성하는 방법

[{
    "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}]

출력:

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

실행할 수 있나요? 물론 가능합니다. 아직 바꾸고 싶지 않은 작은 버그가 있습니다.

4. 사용 시나리오

뭐, 실제로 쓸모없다고 하는 분들도 계시죠? OA 시스템은 항상 유용할 것입니다.

위 내용은 PHP는 어휘 분석 및 사용자 정의 언어도 구현할 수 있습니다!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제