好吧!我承认我想标题党了。大家对解释器的吸引,绝对没有自己动手写一个脚本语言更有吸引力。不过如果看到标题过来的,可能也是

我承认,之前收藏的减肥视频,我都是这样对待他们的。

不过我还是相信很多程序猿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(写一个最简单的脚本语言)的更多相关文章

  1. Lua 是一个小巧的脚本语言

    Redis进阶实践之七Redis和Lua初步整合使用 一.引言 Redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运 ...

  2. webview的简单介绍和手写一个H5套壳的webview

    1.webview是什么?作用是什么?和浏览器有什么关系? Webview 是一个基于webkit引擎,可以解析DOM 元素,展示html页面的控件,它和浏览器展示页面的原理是相同的,所以可以把它当做 ...

  3. 一个Java编写的小玩意儿--脚本语言解释器(一)

    今天开始想写一个脚本语言编译器.在这个领域,我还是知道的太少了,写的这个过程肯定是艰辛的,因为之前从来没有接触过这类的东西.写在自己的博客里,算是记录自己的学习历程吧.相信将来自己有幸再回过头来看到自 ...

  4. 分享一个CQRS/ES架构中基于写文件的EventStore的设计思路

    最近打算用C#实现一个基于文件的EventStore. 什么是EventStore 关于什么是EventStore,如果还不清楚的朋友可以去了解下CQRS/Event Sourcing这种架构,我博客 ...

  5. Makefile 一点一滴(一)—— 从最简单的makefile模板写起

    我在网上先找了一个最简单的makefile. 建立一个 TestCpp 目录,简单的写几行代码,命名为“TestCpp.cpp”,然后和这个最简单的 makefile 一起扔进去: TestCpp.c ...

  6. Java使用poi对Execl简单_读和写_操作

    1 /** 一.简单读取Execl的步骤: * 1.通过流来读取Execl并存放到内存中: * 2.通过WorkbookFactory工作簿工厂来读取内存中存放的execl文档流并创建出一个工作簿 * ...

  7. 郑晔谈 Moco 框架的开发:写一个好的内部 DSL ,写一个表达性好的程序

    作者:张龙 出处:http://www.infoq.com/cn/news/2013/07/zhengye-on-moco 郑晔谈Moco框架的开发:写一个好的内部DSL,写一个表达性好的程序 作者  ...

  8. java最简单的知识之创建一个简单的windows窗口,利用Frame类

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 QQ986945193 微博:http://weibo.com/mcxiaobing 首先给大家看一下 ...

  9. 简单明朗的 RNN 写诗教程

    目录 简单明朗的 RNN 写诗教程 数据集介绍 代码思路 输入 and 输出 训练集构建 生成一首完整的诗 代码实现 读取文件 统计字数 构建word 与 id的映射 转成one-hot代码 随机打乱 ...

随机推荐

  1. hdu 1071 The area【定积分】

    用顶点式\( a(x-h)^2+k=y \)解方程,转化为\(ax^2+bx+c=y \)的形式,然后对二次函数求定积分\( \frac{ax^3}{3}+\frac{bx^2}{2}+cx+C \) ...

  2. UVA - 10859 Placing Lampposts 放置街灯

    Placing Lampposts 传送门:https://vjudge.net/problem/UVA-10859 题目大意:给你一片森林,要求你在一些节点上放上灯,一个点放灯能照亮与之相连的所有的 ...

  3. Poj 2516 Minimum Cost (最小花费最大流)

    题目链接: Poj  2516  Minimum Cost 题目描述: 有n个商店,m个仓储,每个商店和仓库都有k种货物.嘛!现在n个商店要开始向m个仓库发出订单了,订单信息为当前商店对每种货物的需求 ...

  4. 有关LD_LIBRARY_PATH与ld.so.conf

    我之前写过一篇关于LD_LIBRARY_PATH与gcc/g++ -L的关系的文章,于是我自己用CPACK制作了一个Debian安装包,然后我在/home/.bashrc里添加了export LD_L ...

  5. [转]Asp.net MVC中Html.Partial, RenderPartial, Action,RenderAction 区别和用法

    本文转自:http://www.cnblogs.com/gesenkof99/archive/2013/06/03/3115052.html Partial 和RenderPartial:这两个的性质 ...

  6. [转]C#委托Action、Action<T>、Func<T>、Predicate<T>

    CLR环境中给我们内置了几个常用委托Action. Action<T>.Func<T>.Predicate<T>,一般我们要用到委托的时候,尽量不要自己再定义一 个 ...

  7. jquery + ajax 实现多条件查询

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="JquerySort.aspx. ...

  8. CF949B A Leapfrog in the Array

    思路: 最终的时候,对于位置p,若p是奇数,则该位置的元素是(p + 1) / 2:若p是偶数,需要从p开始不断地迭代寻找上一次跳跃所处的位置(p = p + n - p / 2),直到p是奇数为止. ...

  9. ES之各种运算符,for、while、do while 、switch case循环

    运算符优先级: 在所有的运算符中,括号的优先级最高,赋值符号的优先级最低. 小括号 > 计算运算符 > 比较运算符 > 逻辑运算符 > 赋值符号———————————————— ...

  10. WP7 开发资料

    前言 离Windows Phone 7正式发布已过去几个月了,但国内关于Windows Phone 7的中文书籍资料太少了,大多数是英文资料,一本真正的中文开发教程书都没有, 要啃英文资料对大部分的开 ...