// 实验存档

运行截图:

代码中的总体转化流程:中缀表达式字符串→tokens→逆波兰tokens(即后缀表达式)→四元式。

由后缀表达式写出四元式非常容易,比较繁琐的地方在于中缀转逆波兰,这里采用的方法如下↓

通过维护一个符号栈(或者说运算符栈)来处理运算符间的优先级关系。从左至右读入元素:

  1. 该元素是数字,则直接输出该数字
  2. 该元素是算数运算符:
    1. 直接压入符号栈的情况:符号栈为空,或者该运算符优先级大于栈顶运算符
    2. 不断弹出(同时输出该运算符)再压入的情况:符号栈不为空,或者该运算符优先级小于等于栈顶运算符
  3. 该元素是左括号,则直接将左括号压入符号栈,并赋予最小的优先级,避免被弹出。
  4. 该元素是右括号,则不断弹出(同时输出该运算符)符号栈中的元素,直到找到左括号,将左括号弹出但不输出(后缀表达式中是没有括号的)。
  5. 该元素是输入终止符号,则弹出(同时输出该运算符)符号栈中所有元素。

代码:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
</head> <body>
<script>
let str = '4*(28+81*6-75)/8';
let tokens = tokenizer(str);
let inversePolishNotation = getInversePolishNotation(tokens);
let threeAddressCode = getThreeAddressCode(inversePolishNotation); console.log("输入:" + str);
console.log("逆波兰式:" + inversePolishNotation.map(x => x.value));
console.log("四元式:" + threeAddressCode.map(x => x + '\n')); // 获取逆波兰式相应的四元式
function getThreeAddressCode(inversePolishNotation) {
let result = [];
let stack = [];
let index = 0; // 临时变量序号
for (let i = 0; i != inversePolishNotation.length; ++i) {
if (inversePolishNotation[i].tag == '数字') {
stack.push(inversePolishNotation[i]);
} else if (inversePolishNotation[i].tag == '算数运算符') {
let right = stack.pop(); // 右操作数应该是后入栈的那个
let left = stack.pop();
let temp = {
tag: '临时变量',
value: 't' + index++,
};
stack.push(temp);
if (left && right) { // 如果左右操作数都不为空
result.push(`(${inversePolishNotation[i].value}, ${left.value}, ${right.value}, ${temp.value})`);
} else {
throw new Error("缺少操作数,非法运算!");
}
} else {
throw new Error("无法处理的token类型:" + tokens[i].tag);
}
}
return result;
} // 输入中缀形式的tokens,输出逆波兰形式的tokens
function getInversePolishNotation(tokens) {
let result = [];
let symbols = []; // 维护一个符号栈,以便处理运算符间的优先级关系
for (let i = 0; i != tokens.length; ++i) {
if (tokens[i].tag == '数字') {
result.push(tokens[i]);
} else if (tokens[i].tag == '算数运算符') {
if (symbols.length == 0 || symbols[symbols.length - 1].priority < tokens[i].priority) {
symbols.push(tokens[i]);
} else {
while (symbols.length != 0 && symbols[symbols.length - 1].priority >= tokens[i].priority) {
result.push(symbols.pop());
}
symbols.push(tokens[i]);
}
} else if (tokens[i].value == '(') {
symbols.push(tokens[i]);
} else if (tokens[i].value == ')') {
let find = false;
while (symbols.length != 0) {
let temp = symbols.pop();
if (temp.value == '(') {
find = true;
break;
} else {
result.push(temp);
}
}
if (!find) throw new Error("左括号缺失");
} else {
throw new Error("无法处理的token类型:" + tokens[i].tag);
}
}
while (symbols.length != 0) {
let temp = symbols.pop();
if (temp.value == '(') {
throw new Error("右括号缺失");
} else {
result.push(temp);
}
}
return result;
} // 重用之前的词法分析程序
function tokenizer(input) {
let s = input;
let cur = 0;
let peek = ' ';
let line = 1; let readChar = () => s[cur++];
let undo = () => cur--;
let scan = () => { // 每次scan返回一个Token
// 略过空格,上次设置的peek值并不会被清空
for (;; peek = readChar()) {
if (peek == undefined) {
return null; // 读完了
} else if (peek == ' ' || peek == '\t') {
continue; // 略过空格和Tab
} else if (peek == '\n') {
line++; // 记录当前行
} else {
break;
}
} if (/[0-9.]/.test(peek)) {
let temp = peek;
let hasPoint = false;
if (peek == '.') hasPoint = true;
while (/[0-9.]/.test(peek = readChar())) {
if (peek == '.' && hasPoint) {
console.log("第" + line + "行存在语法错误,数字中包含多个小数点");
return null;
} else if (peek == '.') {
hasPoint = true;
temp += peek;
} else {
temp += peek;
}
}
return {
tag: '数字',
value: Number(temp),
};
} if (/[+*/-]/.test(peek)) {
let result = {
tag: '算数运算符',
value: peek,
};
if (peek == '+' || peek == '-') {
result.priority = 1; // 加减号的优先级较低
} else if (peek == '*' || peek == '/') {
result.priority = 2; // 乘除号的优先级较高
}
peek = ' ';
return result;
} if (peek == '(') {
peek = ' ';
return {
tag: '括号',
value: '(',
priority: -99, // 左括号的优先级设置为最小,
// 不会因为除读到右括号外的情况而出栈
};
} if (peek == ')') {
peek = ' ';
return {
tag: '括号',
value: ')',
};
} throw new Error("读入非法字符: " + peek);
}; let tokens = [];
let token;
while (token = scan()) {
tokens.push(token);
}
return tokens;
}
</script>
</body> </html>

编译原理 #04# 中缀表达式转化为四元式(JavaScript实现)的更多相关文章

  1. 【编译原理】c++实现自下而上语法分析及中间代码(四元式)生成

    写在前面:本博客为本人原创,严禁任何形式的转载!本博客只允许放在博客园(.cnblogs.com),如果您在其他网站看到这篇博文,请通过下面这个唯一的合法链接转到原文! 本博客全网唯一合法URL:ht ...

  2. [Swust OJ 322]--东6宿舍灵异事件(中缀表达式转化为后缀表达式的简单运用)

    题目链接:http://acm.swust.edu.cn/problem/322/ Time limit(ms): 1000 Memory limit(kb): 65535     Descripti ...

  3. python实现算术表达式的词法语法语义分析(编译原理应用)

    本学期编译原理的一个大作业,我的选题是算术表达式的词法语法语义分析,当时由于学得比较渣,只用了递归下降的方法进行了分析. 首先,用户输入算术表达式,其中算术表达式可以包含基本运算符,括号,数字,以及用 ...

  4. Java 实现《编译原理》中间代码生成 -逆波兰式生成与计算 - 程序解析

    Java 实现<编译原理>中间代码生成 -逆波兰式生成与计算 - 程序解析 编译原理学习笔记 (一)逆波兰式是什么? 逆波兰式(Reverse Polish notation,RPN,或逆 ...

  5. 《编译原理》画 DAG 图与求优化后的 4 元式代码- 例题解析

    <编译原理>画 DAG 图与求优化后的 4 元式代码- 例题解析 DAG 图(Directed Acylic Graph)无环路有向图 (一)基本块 基本块是指程序中一顺序执行的语句序列, ...

  6. [LeetCode] 由 “中缀表达式 --> 后缀表达式" 所想

    如何利用栈解决问题. Ref: 如何在程序中将中缀表达式转换为后缀表达式? 本文的引申:如何手写语法分析器 实现调度场算法 “9+(3-1)*3+10/2” --> “9 3 1-3*+ 10 ...

  7. MOOC 编译原理笔记(一):编译原理概述以及程序设计语言的定义

    编译原理概述 什么是编译程序 编译程序指:把某一种高级语言程序等价地转换成另一张低级语言程序(如汇编语言或机器代码)的程序. 高级语言程序-翻译->机器语言程序-运行->结果. 其中编译程 ...

  8. 编译原理实验之SLR1文法分析

    ---内容开始--- 这是一份编译原理实验报告,分析表是手动造的,可以作为借鉴. 基于  SLR(1) 分析法的语法制导翻译及中间代码生成程序设计原理与实现1 .理论传授语法制导的基本概念,目标代码结 ...

  9. 编译原理_P1004

    龙书相关知识点总结 //*************************引论***********************************// 1. 编译器(compiler):从一中语言( ...

随机推荐

  1. < JAVA - 大作业(2)仿qq即时通讯软件 >

    < JAVA - 大作业(2)仿qq即时通讯软件 > 背景 JAVA上机大作业:设计一个仿qq即时通讯软件 任务简要叙述:设计一款仿QQ的个人用户即时通讯软件,能够实现注册,登陆,与好友聊 ...

  2. Docker容器监控

    利用docker compose组合应用并利用scale可以快速对容器进行扩充,而docker compose启动的服务容器都在同一台宿主机上,对于一个宿主机上运行多个容器应用时,容器的运行情况,如: ...

  3. java基础面向对象总结(一)

    年底了,总结下知识点和遇到过的面试题目. 1,如何理解面相对象. ‘万物皆对象’说的没错,听起来挺反感的,问一个说一个.有些话觉得用自己的话讲出来肯俗点,但可以证明你理解了一点.我理解的是:之所以叫面 ...

  4. maven 利用 profile 进行多环境配置

    我们在进行项目的多环境配置时,有很多种方式供我们选择,比如 SpringBoot 自带的 application-dev.yml.maven 的 profile 等.这里介绍的就是如何利用 profi ...

  5. Python 分支、循环、条件与枚举

    单行注释:# 注释内容多行注释:''' 注释内容 '''   Python 中有三种控制流语句: if for while 注:Python 中没有 Switch 这种开关语句 if 语句检测条件真, ...

  6. Python面向对象-概念、类、实例

    OOP——Object Oriented Programming,面向对象编程,是一种程序设计思想.该思想将对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 区别于面向过程的程序设计即把计 ...

  7. 4. abp中的asp.net core模块剖析

    相关模块 AbpAspNetCoreModule AbpAspNetCoreMvcModule AbpAspNetCoreMvcContractsModule abp通过这三个模块加载并配置了 asp ...

  8. 使用GDAL/OGR读写矢量文件

    感觉GIS中矢量相关内容还是挺庞杂的,并且由于版本迭代的关系,使用GDAL/OGR读写矢量的资料也有点不太一样.这里总结了一个读写矢量的示例,实现代码如下: #include <iostream ...

  9. 开源Odoo13更新的模块功能信息(译文)

    本文来源江苏欧度软件:www.odooyun.com 本次Odoo13已于10月初发布,更新的模块有:Odoo会计模块.Odoo活动项目模块.Odoo13审批模块.Odoo评价.客户关系管理(CRM) ...

  10. 微信 电脑版 HOOK(WeChat PC Hook)- 定位dll获取数据和调用功能的地址

    方案一:CE搜索内存数据,OD断点查看堆栈方案二:使用旧版本的特征码,在新版本搜索方案三:借鉴WeTool的dll,用ida分析获取地址方案四:ida静态分析微信,看字符串和输出日志 源码: http ...