0
点赞
收藏
分享

微信扫一扫

编译原理: 做一个LL(1)语法分析器

waaagh 2022-02-13 阅读 73

本文实现了简易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';
    }
}
举报

相关推荐

0 条评论