用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代码 随机打乱 ...
随机推荐
- 洛谷 P4011 孤岛营救问题【bfs】
注意: 一个点可能有多把钥匙,所以把每个点有钥匙的情况状压一下 两个点之间有障碍的情况只给出了单向,存的时候记得存一下反向 b[i][j]表示当前点拥有钥匙的状态,g[x1][y1][x2][y2]表 ...
- 在ios Xcode10下小白都能解决library not found for -libstdc++.6.0.9
写在前面 library not found for -libstdc++.6.0.9,今天做项目的时候碰到这个问题,解决的过程中遇到了目录路径不对的问题(不在通常的/Applications/Xco ...
- java String类为什么是final的
1.为了安全 java 必须借助操作系统本身的力量才能做事,jdk提供的很多核心类比如String,这类内的很多方法 都不是java编程语言本身编写的,很多方法都是调用操作系统本地的api,如果被继承 ...
- InputFilter在过滤空格时重复输入的问题
正确做法:editText.setFilters(new InputFilter[] { new InputFilter() { @Override public CharSequence filte ...
- 【洛谷3343_BZOJ3925】[ZJOI2015]地震后的幻想乡(状压 DP_期望)
题目: 洛谷 3343 BZOJ 3925 分析: 谁给我说这是个期望概率神题的,明明没太大关系好吧 「提示」里那个结论哪天想起来再问 Jumpmelon 怎么证. 首先,由于开始修路前 \(e_i\ ...
- 对比度受限的自适应直方图均衡化(CLAHE)
直方图均衡化(HE)是一种很常用的直方图类方法,基本思想是通过图像的灰度分布直方图确定一条映射曲线,用来对图像进行灰度变换,以达到提高图像 对比度的目的.该映射曲线其实就是图像的累计分布直方图(CDF ...
- hdu 1430 魔板 康托展开 + 很好的映射
http://acm.hdu.edu.cn/showproblem.php?pid=1430 如果从start ---> end,每一次都bfs进行,那么就肯定会超时. 考虑到先把start映射 ...
- java.lang.String 字符串操作
1.获取文件名 //获取文件名,即就是去掉文件的后缀 /** * mypic.jpg * 获取文件名 * 1. 先找到"."的位置 * 2. 从第一个字符开始截取到".& ...
- R in action读书笔记(6)-第七章:基本统计分析(中)
7.2 频数表和列联表 > library(vcd) > head(Arthritis) ID Treatment Sex Age Improved 1 57 Treated Male 2 ...
- js模块化方案以及前端打包工具
图片来自知乎