加入收藏 | 设为首页 | 会员中心 | 我要投稿 阜阳站长网 (https://www.0558zz.cn/)- AI行业应用、低代码、混合云存储、数据仓库、物联网!
当前位置: 首页 > 运营中心 > 建站资源 > 优化 > 正文

PHP也可以实现词法分析与自定义语言!

发布时间:2022-08-11 11:32:02 所属栏目:优化 来源:互联网
导读:之前项目有一个需求,业务人员使用中文编写一些自定义公式,然后需要我们后台执行将结果返回到界面上,于是就基于有限状态机写了这个词法分析器,比较简单,希望能够抛砖引玉。 一、分析需求 输入中文公式,返回结果,比如: 现有薪资=10000; 个税起点=3000;
之前项目有一个需求,业务人员使用中文编写一些自定义公式,然后需要我们后台执行将结果返回到界面上,于是就基于有限状态机写了这个词法分析器,比较简单,希望能够抛砖引玉。
 
一、分析需求
 
输入中文公式,返回结果,比如:
 
 
 
 
 
 
 
 
现有薪资=10000;
 
个税起点=3000;
 
当前年份=2021;
 
如果(当前年份=2022){
 
    个税起点=5000;
 
}
 
返回 (现有薪资-个税起点) * 0.2;
 
二、实现需求
 
最初的想法是使用字符串替换的方式,将中文关键字替换成php的关键字,然后调用eval执行,这样确实也是可以的,但是总觉得不是很美丽,并且不能实现动态解析。就想着自己实现一个简单的词法分析,然后结合ast将词法转换成php代码执行,岂不快哉。当前版本没有用到抽象语法树来生成代码,全部使用字符串拼接。【推荐学习:PHP视频教程】
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<?php
 
 
 
/**
 
 * Class Lexer
 
 * @package SettOaLang
 
 * 词法分析器
 
 */
 
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('eof', $this->currChar);
 
        }
 
        return $this->currToken(static::VAR, $this->currChar);
 
    }
 
 
 
    /**
 
     * @param string $input
 
     * @return string
 
     */
 
    private function matchVar(string $input = "") {
 
        $word = $input ?: '';
 
        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('a') && $ord < ord('z')) {
 
            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;
 
    }
 
 

(编辑:阜阳站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读