用PHP写一个最简单的解释器Part4(写一个最简单的脚本语言)
好吧!我承认我想标题党了。大家对解释器的吸引,绝对没有自己动手写一个脚本语言更有吸引力。不过如果看到标题过来的,可能也是
我承认,之前收藏的减肥视频,我都是这样对待他们的。
不过我还是相信很多程序猿or程序媛不仅仅希望可以做出一个牛逼的产品,更想做出来一个牛逼闪闪的编程语言。而这里就是朝着开发一个脚本语言的方向去努力
恩!我就是xxoo语言之父。
前几篇文章已经实现一个简易的加减法计算器,想必看了文章之后可以很快的写出来一个乘除法计算器。那么如果加减乘除混合运算呢?
这节将实现类似6-1*2+4/2这样的运算方式,大家看到这个式子之后应该已经了解难点在哪里了。好了下面开始,首先展示完整的代码。
<?php
define('ISINTEGER','ISINTEGER');//定义整数类型描述
define('PLUS','PLUS');//定义操作符号类型描述 加法
define('MINUS','MINUS');//定义操作符号类型描述 减法
define('MUL','MUL');//定义操作符号类型描述 乘法
define('DIV','DIV');//定义操作符号类型描述 除法
define('WHITESPACE',' ');//定义空格
/**
Token  用来存储输入字符的类型
*/
class Token{
    private $type;
    private $value;
    /**
    $type ISINTEGER/PLUS/MINUS
    $value 对应的字符串
    */
    public function __construct($type,$value)
    {
        $this->type=$type;
        $this->value=$value;
    }
    /**
    通过该方法来获取类的私有属性
    */
    public function __get($name)
    {
        return $this->{$name};
    }
    /**
    用于调试
    */
    public function __toString()
    {
        return 'type:'.$this->type.' value:'.$this->value;
    }
}
class Lexer{
    private $current_char ;
    private $current_token ;
    private $text;
    private $pos=0;
    /***
    $text 需要进行解释的字符串
    */
    public function __construct($text){
        //去除前后可能存在的空格 这些空格是无效的
        $this->text=trim($text);
        //初始化 获取第一个字符
        $this->current_char = $this->text[$this->pos];
    }
    public function error()
    {
        throw new \Exception('Lexer eroor');
    }
    /*
    步进方法,每操作一个字符后前进一位
    */
    public function advance()
    {
        $this->pos++;
        if ($this->pos>strlen($this->text)-1){
            $this->current_char=null;
        }else{
            $this->current_char=$this->text[$this->pos];
        }
    }
    /*
    去除空格
    */
    public function skip_whitespace()
    {
        if ($this->current_char!=null&&$this->current_char==WHITESPACE){
            $this->advance();
        }
    }
    /*
    如果要支持多位的整数,则需要将每位数字存储起来
    */
    public function integers()
    {
        $result='';//用于存储数字
        while($this->current_char!=null&&is_numeric($this->current_char)){//只要当前字符是数字就一直循环并将数字存储于$result
            $result.=$this->current_char;
            $this->advance();//步进方法,每操作一个字符后前进一位
        }
        return intval($result);//将数字字符串转成整数
    }
    //获取当前字符的Token
    public function get_next_token()
    {
        while($this->current_char!=null){
            if ($this->current_char==WHITESPACE){
                $this->skip_whitespace();
                continue;
            }
            if (is_numeric($this->current_char)){
                return new Token(ISINTEGER,$this->integers());
            }
            if ($this->current_char=="+"){
                $this->advance();
                return new Token(PLUS,'+');
            }
            if ($this->current_char=="-"){
                $this->advance();
                return new Token(MINUS,'-');
            }
            if ($this->current_char=="*"){
                $this->advance();
                return new Token(MUL,'*');
            }
            if ($this->current_char=="/"){
                $this->advance();
                return new Token(DIV,'/');
            }
            return new Token('EOF', null);
        }
    }
}
//解释器
class Interpreter{
    private $current_token ;
    private $lexer ;
    public function __construct($lexer){
        //去除前后可能存在的空格 这些空格是无效的
        $this->lexer=$lexer;
        //初始化 获取第一个字符
        $this->current_token=$this->lexer->get_next_token();
    }
    //如果字符类型和判断的类型一致,则继续,否则输入错误
    public function eat($token_type)
    {
        if ($this->current_token->type==$token_type){
            $this->current_token=$this->lexer->get_next_token();
        }else{
            $this->error();
        }
    }
    public function error()
    {
        throw new \Exception('eroor');
    }
    public function factor()
    {
        $token=$this->current_token;
        $this->eat(ISINTEGER);
        return $token->value;
    }
    public function term()
    {
        $result=$this->factor();
        while(in_array($this->current_token->type,[MUL,DIV])){
            $token=$this->current_token;
            if ($token->type==MUL){
                $this->eat(MUL);
                $result=$result*$this->factor();
            }
            else if ($token->type==DIV){
                $this->eat(DIV);
                $result=$result/$this->factor();
            }
        }
        return $result;
    }
    //解释方法
    public function expr()
    {
        $result=$this->term();
        while(in_array($this->current_token->type,[PLUS,MINUS])){
            $token=$this->current_token;
            if ($token->type==PLUS){
                $this->eat(PLUS);
                $result=$result+$this->term();
            }
            else if ($token->type==MINUS){
                $this->eat(MINUS);
                $result=$result-$this->term();
            }
        }
        return $result;
    }
}
do{
    fwrite(STDOUT,'xav>');;
    $input=fgets(STDIN);
    $Interpreter=new Interpreter(new Lexer($input));
    echo $Interpreter->expr();
    unset($Interpreter);
}while(true);
看过前面几篇文章的已经了解过,这里的代码结构发生了变化。
首先,分离出来词法分析类,该类的作用:将字符串进行类似分词处理,并将每个词的含义返回
class Lexer{
    private $current_char ;
    private $current_token ;
    private $text;
    private $pos=0;
    /***
    $text 需要进行解释的字符串
    */
    public function __construct($text){
        //去除前后可能存在的空格 这些空格是无效的
        $this->text=trim($text);
        //初始化 获取第一个字符
        $this->current_char = $this->text[$this->pos];
    }
    public function error()
    {
        throw new \Exception('Lexer eroor');
    }
    /*
    步进方法,每操作一个字符后前进一位
    */
    public function advance()
    {
        $this->pos++;
        if ($this->pos>strlen($this->text)-1){
            $this->current_char=null;
        }else{
            $this->current_char=$this->text[$this->pos];
        }
    }
    /*
    去除空格
    */
    public function skip_whitespace()
    {
        if ($this->current_char!=null&&$this->current_char==WHITESPACE){
            $this->advance();
        }
    }
    /*
    如果要支持多位的整数,则需要将每位数字存储起来
    */
    public function integers()
    {
        $result='';//用于存储数字
        while($this->current_char!=null&&is_numeric($this->current_char)){//只要当前字符是数字就一直循环并将数字存储于$result
            $result.=$this->current_char;
            $this->advance();//步进方法,每操作一个字符后前进一位
        }
        return intval($result);//将数字字符串转成整数
    }
    //获取当前字符的Token
    public function get_next_token()
    {
        while($this->current_char!=null){
            if ($this->current_char==WHITESPACE){
                $this->skip_whitespace();
                continue;
            }
            if (is_numeric($this->current_char)){
                return new Token(ISINTEGER,$this->integers());
            }
            if ($this->current_char=="+"){
                $this->advance();
                return new Token(PLUS,'+');
            }
            if ($this->current_char=="-"){
                $this->advance();
                return new Token(MINUS,'-');
            }
            if ($this->current_char=="*"){
                $this->advance();
                return new Token(MUL,'*');
            }
            if ($this->current_char=="/"){
                $this->advance();
                return new Token(DIV,'/');
            }
            return new Token('EOF', null);
        }
    }
}
其实四则运算最为复杂的部分就是如果解决先乘除后加减。这里将其分成了两个部分。term函数就是在进行乘除法部分,expr则进行加法
public function term()
    {
        $result=$this->factor();
        while(in_array($this->current_token->type,[MUL,DIV])){
            $token=$this->current_token;
            if ($token->type==MUL){
                $this->eat(MUL);
                $result=$result*$this->factor();
            }
            else if ($token->type==DIV){
                $this->eat(DIV);
                $result=$result/$this->factor();
            }
        }
        return $result;
    }
    //解释方法
    public function expr()
    {
        $result=$this->term();
        while(in_array($this->current_token->type,[PLUS,MINUS])){
            $token=$this->current_token;
            if ($token->type==PLUS){
                $this->eat(PLUS);
                $result=$result+$this->term();
            }
            else if ($token->type==MINUS){
                $this->eat(MINUS);
                $result=$result-$this->term();
            }
        }
        return $result;
    }

图很丑,不过还是希望你可以看懂,也就是4-2*3-1 ,expr中乘除都是一个整体,其会被在term中进行运算返回,这样这个式子就分成了,4-积-1.而积是在term中进行运算的。
文中很多描述存在很多不当,我也在积极的学习如何将知识讲的通俗易懂。如果您有好的办法,可以一同探讨
原文地址:https://segmentfault.com/a/1190000015623736
用PHP写一个最简单的解释器Part4(写一个最简单的脚本语言)的更多相关文章
- Lua 是一个小巧的脚本语言
		
Redis进阶实践之七Redis和Lua初步整合使用 一.引言 Redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运 ...
 - webview的简单介绍和手写一个H5套壳的webview
		
1.webview是什么?作用是什么?和浏览器有什么关系? Webview 是一个基于webkit引擎,可以解析DOM 元素,展示html页面的控件,它和浏览器展示页面的原理是相同的,所以可以把它当做 ...
 - 一个Java编写的小玩意儿--脚本语言解释器(一)
		
今天开始想写一个脚本语言编译器.在这个领域,我还是知道的太少了,写的这个过程肯定是艰辛的,因为之前从来没有接触过这类的东西.写在自己的博客里,算是记录自己的学习历程吧.相信将来自己有幸再回过头来看到自 ...
 - 分享一个CQRS/ES架构中基于写文件的EventStore的设计思路
		
最近打算用C#实现一个基于文件的EventStore. 什么是EventStore 关于什么是EventStore,如果还不清楚的朋友可以去了解下CQRS/Event Sourcing这种架构,我博客 ...
 - Makefile 一点一滴(一)—— 从最简单的makefile模板写起
		
我在网上先找了一个最简单的makefile. 建立一个 TestCpp 目录,简单的写几行代码,命名为“TestCpp.cpp”,然后和这个最简单的 makefile 一起扔进去: TestCpp.c ...
 - Java使用poi对Execl简单_读和写_操作
		
1 /** 一.简单读取Execl的步骤: * 1.通过流来读取Execl并存放到内存中: * 2.通过WorkbookFactory工作簿工厂来读取内存中存放的execl文档流并创建出一个工作簿 * ...
 - 郑晔谈 Moco 框架的开发:写一个好的内部 DSL ,写一个表达性好的程序
		
作者:张龙 出处:http://www.infoq.com/cn/news/2013/07/zhengye-on-moco 郑晔谈Moco框架的开发:写一个好的内部DSL,写一个表达性好的程序 作者 ...
 - java最简单的知识之创建一个简单的windows窗口,利用Frame类
		
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 QQ986945193 微博:http://weibo.com/mcxiaobing 首先给大家看一下 ...
 - 简单明朗的 RNN 写诗教程
		
目录 简单明朗的 RNN 写诗教程 数据集介绍 代码思路 输入 and 输出 训练集构建 生成一首完整的诗 代码实现 读取文件 统计字数 构建word 与 id的映射 转成one-hot代码 随机打乱 ...
 
随机推荐
- Logstash同步mysql数据库信息到ES
			
@font-face{ font-family:"Times New Roman"; } @font-face{ font-family:"宋体"; } @fo ...
 - 5分钟了解Python语言的简单介绍(转载)
			
< 转载于Python数据之道 - 本公众号秉承“让数据更有价值”的理念,主要分享数据相关的内容,包括数据分析,挖掘,可视化,机器学习,深度学习等.> ...
 - [转]广义正交匹配追踪(gOMP)
			
广义正交匹配追踪(Generalized OMP, gOMP)算法可以看作为OMP算法的一种推广,由文献[1]提出,第1作者本硕为哈工大毕业,发表此论文时在Korea University攻读博士学位 ...
 - 暑期训练狂刷系列——Foj 1894  志愿者选拔 (单调队列)
			
题目连接: http://acm.fzu.edu.cn/problem.php?pid=1894 解题思路: 因为出队和入队都满足队列的性质,优先单调队列的优先级有进入队列的顺序和人品的高低,在一段区 ...
 - [GZOI2016] 亚索的量子实验【分块】
			
第二题 亚索的粒子实验 [问题描述] 亚索是一名伟大的科学家,他最近在做一个粒子的实验,粒子初始有一定的能量,实验过程中倘若第i个粒子被注入k能量,那该粒子就会增加k能量,同时由于辐射作用,第2i,3 ...
 - 贪心 HDOJ 5385 The Path
			
题目传送门 /* 题意:给一张图和一些有向边,问如何给边赋值使得d1 < d2 < .. < dx > ,,,< ddn 贪心:左边从2开始,右边从n开始,每次选择未标记 ...
 - 员工管理系统(集合与IO流的结合使用  beta3.0  BufferedReader / ObjectOutputStream)
			
Employee.java package cn.employee_io; public class Employee { private String empId; private String n ...
 - Netflix正式开源其API网关Zuul 2--转
			
微信公众号:聊聊架构 5 月 21 日,Netflix 在其官方博客上宣布正式开源微服务网关组件 Zuul 2.Netflix 公司是微服务界的楷模,他们有大规模生产级微服务的成功应用案例,也开源了相 ...
 - SpringSecurity的简单使用
			
导入SpringSecurity坐标 在web.xml中配置过滤器 编写spring-securiy配置文件 编写自定义认证提供者 用户新增时加密密码 配置页面的login和logout 获取登录用户 ...
 - REMOVE A WINDOWS SERVICE
			
You can easily remove a Windows service from the Windows registry using a simple command prompt comm ...