本文实现了简易LL1语法分析器
最终效果
实验语言
html、css、js
实验环境
Visual Studio Code、Google Chrome
设计概要
输入一组符合LL(1)文法的产生式,分析出其first集、follow集,并且构造其预测分析表,最后用此预测分析表判断某串是否符合此组产生式的规则。
补充
-
由于作业时间仓促,因此 此语法分析器还有
许多不足之处,例如:
- 1.没有分析输入文法是否是LL(1)文法、
- 2.没有做第四部分输入串的显示(只能在源代码里填写)
- 3.代码优化等
- 4.敬请大佬指正
代码
代码1— index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
//样式部分
<style>
*{
padding: 0;
margin: 0 auto;
}
div{
width: 500px;
height: 1000px;
position: relative;
left: -165px;
}
.input_num{
display: block;
position: absolute;
z-index: 100;
width: 500px;
height: 50px;
line-height: 50px;
text-align: center;
left: 150px;
top: 7px;
}
.input_num input{
display: block;
width: 100px;
height: 30px;
border-radius: 17px;
outline: none;
border: 1px solid gray;
padding: 0 10px;
position: absolute;
left: 205px;
top: 7px;
text-align: center;
}
.text{
display: block;
width: 170px;
height: 30px;
line-height: 30px;
text-align: center;
position: absolute;
left: 40px;
top: 7px;
}
.submit{
display: block;
width: 50px;
height: 26px;
line-height: 26px;
text-align: center;
background-color: pink;
opacity: .7;
position: absolute;
left: 355px;
top: 9px;
border-radius: 15px;
font-size: .5em;
cursor: pointer;
}
.input_pro{
width: 500px;
height: auto;
position: absolute;
top: 80px;
left: 150px;
}
ul,li{
list-style: none;
}
ul{
width: 300px;
height: auto;
border: 3px solid gray;
border-radius: 20px;
font-size: 16px;
padding: 10px 20px 40px 20px;
display: none;
}
li{
width: 100%;
height: 40px;
}
.head{
height: 30px;
line-height: 30px;
}
ul li span{
display: block;
width: 150px;
height: 30px;
line-height: 30px;
text-align: center;
float: left;
}
ul li span input{
width: 90px;
height: 30px;
padding: 0 10px;
border: none;
border-bottom: 1px solid gray;
outline: none;
text-align: center;
}
.submit_pro{
display: block;
width: 50px;
height: 26px;
line-height: 26px;
text-align: center;
background-color: pink;
opacity: .7;
position: absolute;
left: 355px;
top: 367px;
border-radius: 15px;
font-size: .5em;
cursor: pointer;
}
</style>
</head>
<body>
<div>
<div class="input_num">
<span class="text">请输入产生式的个数:</span>
<input type="text" class="getNum">
<span class="submit">确定</span>
</div>
<div class="input_pro">
<ul class="book">
</ul>
</div>
</div>
<script src="tools.js"></script> <!--工具函数文件 -->
//原汁原味的原生JS
<script>
let btn = document.getElementsByClassName('submit')[0];
let input_num = document.getElementsByClassName('getNum')[0];
let ul = document.getElementsByClassName('book')[0];
let inputsLeft = document.getElementsByClassName('left');
let inputsRight = document.getElementsByClassName('right');
let input_pro = document.getElementsByClassName('input_pro')[0];
let submit_pro = document.getElementsByClassName('submit_pro')[0];
let objs = []; //存储所有产生式对象
let EndSymArr = getEndSym(); //获得终结符
let products = new Array();
let rightArr = []; //存储所有产生式右部
btn.onclick = function(){ //点击生成输入产生式的表单
ul.innerHTML = `<li class="head">
<span>产生式左部</span>
<span>产生式右部</span>
</li>`;
for(let i = 0; i < input_num.value; i++){
ul.innerHTML += `<li>
<span><input type="text" class="left"></span>
<span><input type="text" class="right"></span>
</li>`;
}
ul.style.display = 'block';
input_pro.innerHTML += `<span class="submit_pro">提交</span>`;
submit_pro = document.getElementsByClassName('submit_pro')[0];
submit_pro.onclick = function(){ //提交表单,开始分析程序
for(let i = 0; i < input_num.value; i++){ //获得输入的产生式
products[i] = new Array();
products[i][0] = inputsLeft[i].value;
products[i][1] = inputsRight[i].value;
}
for(let i = 0; i < products.length; i++){ //初始化产生式对象
objs.push(new Product(products[i][0], products[i][1]));
}
//获得右部数组,便于getFollow
for(let i = 0; i < objs.length; i++){
rightArr.push(objs[i].right);
}
console.log(objs);
// ---------------------------------------------------------------first集
for(let i = 0; i < objs.length; i++){ //找每个产生式的first集
let now = objs[i];
getFirst(objs, objs[i].right, now);
}
console.log("first集为:"); //输出first集
let tag = objs[0].value;
let firstArr = [];
for(let i = 0; i < objs.length; i++){
if(tag == objs[i].value){
firstArr.push(objs[i].first);
}else{
console.log(objs[i-1].value, firstArr.flat());
tag = objs[i].value;
firstArr = [objs[i].first];
}
if(i == objs.length-1){
console.log(objs[objs.length-1].value, firstArr.flat());
}
}
// ---------------------------------------------------------------follow集
for(let i = 0; i < objs.length; i++){
objs[0].follow.push('#');
getFollow(objs[i].value, rightArr, objs[i], objs);
}
console.log("follow集为:"); //输出follow集
perfect_follow();
for(let k = 0; k < objs.length; k++){
if(k == objs.length-1){
console.log(objs[objs.length-1].value, objs[objs.length-1].follow);
}else{
if(objs[k].value != objs[k+1].value){
console.log(objs[k].value, objs[k].follow);
}
}
}
// ---------------------------------------------------------------预测分析表
EndSymArr = getEndSym(); //获取终结符数组
let resArr = new Array(); //存储结果的数组
for(let i = 0; i < objs.length; i++){ //外层循环对象value值,即非终结符
resArr[i] = new Array();
for(let j = 0; j < EndSymArr.length; j++){ //内层循环终结符数组
if(objs[i].right == '$'){ //考虑产生式右部为空的情况
if(objs[i].follow.includes(EndSymArr[j])){ //查看follow集
resArr[i][j] = objs[i].right;
}else{
resArr[i][j] = 'error';
}
}else{ //产生式右部不为空
if(objs[i].first.includes(EndSymArr[j])){ //查看first集
resArr[i][j] = objs[i].right;
}else{
resArr[i][j] = 'error';
}
}
}
}
for(let i = 0; i < resArr.length; i++){ //将获得的预测分析表 数组赋给相应的对象
objs[i].arr = resArr[i];
}
console.log("预测分析表为:"); //输出预测分析表
console.log(EndSymArr); //终结符
for(let i = 0; i < objs.length; i++){
console.log(objs[i].value, objs[i].arr)
}
// ---------------------------------------------------------------串的分析
let stack = '#'+objs[0].value; //定义符号栈
let str = '#i+i*i'; //定义输入串
let stackLast = stack[stack.length-1]; //存储栈顶
let strLast = str[str.length-1]; //存储字符串首位
let flag = getTab(stackLast, strLast); //存储查表结果
for(let n = 0; n < 100; n++){ //第一次写了死循环,于是觉得这里循环次数写死比较好
if('A' <= stackLast && stackLast <= 'Z'){ //栈顶为非终结符
flag = getTab(stackLast, strLast); //查表
if(flag == '$'){ //查表结果为空则stackLast出栈
console.log('--------',stack,'------', str,'------',flag);
stack = stack.slice(0, -1);
stackLast = stack[stack.length-1];
}else if(flag == 'error'){ //查表结果为'error'直接报错!
console.log('出错!');
break;
}else{ //查表结果为'right'则替换
console.log('--------',stack,'------', str,'------',flag);
stack = stack.replace(stackLast, flag.split('').reverse().join(''));
stackLast = stack[stack.length-1];
}
}else if(stackLast == strLast && stackLast != '#'){ //栈顶为终结符==strLast
console.log('--------',stack, '------',str,'------',stackLast,'匹配');
stack = stack.slice(0, -1);
stackLast = stack[stack.length-1]; //出栈
str = str.slice(0, -1);
strLast = str[str.length-1]; //分析串-1
}else if(stackLast == '#'){ //stackLast == strLast == '#' 匹配成功
console.log('--------',stack,'------', str,'------','恭喜你!匹配成功!!!');
break;
}
}
}
}
</script>
</body>
</html>
代码2— tools.js
function Product(value,right){ //定义产生式构造函数
this.value = value; //存产生式左部
this.right = right; //存产生式右部
this.first = []; //存储first集
this.follow = []; //存储follow集
this.arr = []; //存储预测分析表(自己的那一行)
}
Array.prototype.unique = function () { //数组去重2(利用对象不重名的原理)
var obj = {},
arr = [],
len = this.length; //谁调用,就是谁的length
for(var i = 0; i < len; i++) { //遍历数组
if(!obj[this[i]]) { //如果以数组成员为名的对象成员值为undefined
obj[this[i]] = 'abc'; //则为其赋值,说明为第一次创建
arr.push(this[i]); //并加入到新数组中
}
}
return arr;
}
// 以下是getfirst部分
function find(objs, which){ //服务于getfirst的递归
let find_arr = [];
for(let i = 0; i < objs.length; i++){
if(objs[i].value == which){
find_arr.push(objs[i]);
}
}
return find_arr;
}
function getFirst(objs, right ,now){ //获取first集
let findRes_arr = {};
if(!('A' <= right.charAt(0) && right.charAt(0) <= 'Z')){ //right(1)是终结符
now.first.push(right.charAt(0)); //直接加入first集
}else{ //非终结符则进入递归
findRes_arr = find(objs, right.charAt(0)); //寻找谁要进入递归
for(let i = 0; i < findRes_arr.length; i++){
getFirst(objs, findRes_arr[i].right, now);
}
}
}
// 以下是getfollow部分
function findRight(left, arr){
let res_arr = [];
for(let i = 0; i < arr.length; i++){
if(arr[i].includes(left)){
res_arr.push(arr[i]);
}
}
return res_arr;
}
function findObj(who){
for(let i = 0; i < objs.length; i++){
if(objs[i].right == who){
return objs[i];
}
}
}
function value_find(who){
let arr = [];
for(let i = 0; i < objs.length; i++){
if(objs[i].value == who){
arr.push(objs[i].first);
}
}
return arr.flat();
}
function follow(arr){
var newArr = [];
for(let i = 0; i < arr.length; i++){
if(typeof(arr[i]) == 'string'){
newArr.push(arr[i]);
}
}
return newArr;
}
function perfect_follow(){
for(let i = 0; i < objs.length; i++){
objs[i].follow = follow(objs[i].follow.flat(10).unique());
}
}
function getFollow(who, rightArr, obj, objs){
let Res = findRight(who, rightArr); //存储符合条件的右部
for(let i = 0; i < Res.length; i++){
let location = Res[i].indexOf(who);//存储目标字符的位置
let target_next = Res[i].slice(location+1, location+2);//存储目标字符的后一个字符
if('A' <= target_next && target_next <= 'Z'){ //目标字符的后一个字符为非终结符
let arr = value_find(target_next); //存储first集
for(let j = 0; j < arr.length; j++){ //循环first集,寻找$
if(arr[j] == '$'){
obj.follow.push(findObj(Res[i],objs).follow);
}else{
obj.follow.push(arr[j]);
}
}
}else if(location == Res[i].length - 1){ //目标字符后面为空
if(findObj(Res[i]).follow.length > 0){
obj.follow.push(findObj(Res[i]).follow);
}
}else{ //目标字符后面为终结符
obj.follow.push(target_next);
}
}
}
// 以下是预测分析表部分
function getEndSym(){ //获取非终结符数组
let arr = [];
for(let i = 0; i < objs.length; i++){
arr.push(objs[i].first,objs[i].follow);
}
arr = arr.flat().unique();
arr.splice(arr.indexOf('$'), 1);//扁平化、数组去重
return arr;
}
// 以下是输入串的分析部分
function findESNum(ES){ //查找非终结符对应的位置
for(let i = 0; i < EndSymArr.length; i++){
if(ES == EndSymArr[i]){
return i;
}
}
}
function getTab(nES, ES){ //查预测分析表(非终结符,终结符)
let flag = true; //标志
let nES_Arr = []; //非终结符数组
let num = findESNum(ES);
for(let i = 0; i < objs.length; i++){ //获取对应的非终结符数组
if(objs[i].value == nES){
nES_Arr.push(objs[i].arr);
}
}
for(let i = 0; i < nES_Arr.length; i++){ //外层遍历非终结符数组
for(let j = 0; j < EndSymArr.length; j++){ //内层遍历终结符
if(nES_Arr[i][num] != 'error'){ //如果不为错,则直接返回,修改flag
flag = false;
return nES_Arr[i][num];
}
}
}
if(flag == true){ //表示全是error,只能返回error
return 'error';
}
}