用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代码 随机打乱 ...
随机推荐
- B - Burning Midnight Oil CodeForces - 165B
One day a highly important task was commissioned to Vasya — writing a program in a night. The progra ...
- 《windows核心编程系列》一谈谈windows中的错误处理机制
错误处理 我们写的函数会用返回值表示程序执行的正确与否,使用void,就意味着程序一定不会出错.Bool类型标识true时为真,false时为假.其他类型根据需要可以定义成不同意义. Windows除 ...
- Codeforces Round #479 (Div. 3)解题代码
A. Wrong Subtraction #include <bits/stdc++.h> using namespace std; int main() { int n,k; cin&g ...
- JS 对输入判断变化屏蔽中文输入法连续输入时触发的事件
//智能搜索提示 IntelligenceSearch: function IntelligenceSearch() { $('#keyWord').on('input', function () { ...
- 221 Maximal Square 最大正方形
在一个由0和1组成的二维矩阵内,寻找只包含1的最大正方形,并返回其面积.例如,给出如下矩阵:1 0 1 0 01 0 1 1 11 1 1 1 11 0 0 1 0返回 4. 详见:https://l ...
- Oracle中的序列
序列是什么? 序列是用来生成唯一.连续的整数的数据库对象.序列通常用来自动生成主键或唯一键的值.序列可以按升序排列,也可以按照降序排列. 其实Oracle中的序列和MySQL中的自增长差不多一个意思. ...
- 纯css实现的三级水平导航菜单
vscode练习使用开发纯css的三级水平导航菜单.先上图: 1.html5布局 <html> <head> <meta charset="UTF-8" ...
- 《基于Node.js实现简易聊天室系列之项目前期工作》
前期工作主要包括:项目的创建,web服务器的创建和数据库的连接. 项目创建 网上关于Node.js项目的创建的教程有很多,这里不必赘述.Demo所使用的Node.js的框架是express,版本为4. ...
- 安卓(Android)关于 RecyclerView 不能填充满宽度
RecyclerView 不能填充满屏幕宽度 RecyclerView 的 Adapter 在使用是,一定要 @Overridepublic RecyclerView.ViewHolder onCre ...
- Farseer.net轻量级ORM开源框架 V1.3版本升级消息
SHA-1: abca3b99801648fa23c7f4934de6c128f042cf47 * 提交新版本:V1.31.重构:FS.Mapping命名空间移到 FS.Core.Map中2.重构:对 ...