【编译原理】LL(1)分析法:C/C++实现

news2024/10/5 10:01:07

 🔖墨香寄清辞:空谷幽篁风动,梦中仙鹤月明。 辗转千秋豪情在,乘风翱翔志不移。

目录

1. 编译原理之LL(1)分析法概念

1.1 编译原理

1.2 LL(1)分析法

2. LL(1)分析法

2.1 实验目的

2.2 实验要求

2.3 实验内容

2.3.1 实验解决代码

2.3.2 运行结果

2.3.3 详细代码分析

2.3.3.1 init()函数

2.3.3.2 analyse()函数

2.4 实验心得


1. 编译原理之LL(1)分析法概念

1.1 编译原理

编译原理是计算机科学领域的一个重要分支,它研究如何将高级编程语言的源代码转化成计算机能够执行的机器代码或中间代码的过程。编译原理涵盖了编译器的设计和实现,其中编译器是一种将源代码翻译成目标代码的软件工具。编译器的主要任务包括语法分析、词法分析、语义分析、优化和代码生成等环节。

1.2 LL(1)分析法

LL(1)分析法是一种常用的自顶向下的语法分析方法,用于分析和解释编程语言或其他形式的文本。LL(1)代表"Left-to-Right, Leftmost derivation, 1 symbol lookahead",这表示了分析器的工作方式和限制条件,通常用于编程语言的语法分析,编写编译器或解释器。主要步骤包括构建LL(1)文法、构建LL(1)分析表和使用递归下降分析或预测分析器等算法来分析输入文本。

🔥 资源获取:关注公众号【科创视野】回复  LL分析法源码

🔥 相关博文:编译原理之逆波兰式的产生及计算:C/C++实现(附源码+详解!)


2. LL(1)分析法

2.1 实验目的

(1)加深对预测分析LL(1)分析法的理解;

(2)根据某一文法编制调试LL(1)分析程序,以便对任意输入的符号串分析。


2.2 实验要求

实验规定对下列文法,用LL(1)分析法对任意输入的符号串进行分析,具体文法如下:

(1)E::=TG

(2)G::=+TG

(3)G::=ε

(4)T::=FS

(5)S::=*FS

(6)S::=ε

(7)F::=(E)

(8)F::=i

若输入串为

i+i*i#

则输出:

​​

LL(1)的分析表:

   i

   +

   *

  (

    )

   #

    说    明

 E

   e

   e

Select(E→TG)={(,i}

 G

g

g1

g1

Select (G→+TG)={+}

Select (G→є)={#,)}

 T

   t

 t

Select (T→FS)={(,i}

 S

s1

   s

  s1

  s1

Select (S→*FS)={*}Select (S→є)={#,) +}

 F

  f1

   F

Select (F→(E))={(}

Select (F→i)={i}

参考代码(不完整):

do/*读入分析串*/
{
    scanf("%c",&ch);
    if ((ch!='i') &&(ch!='+') &&(ch!='*')&&(ch!='(')&&(ch!=')')&&(ch!='#'))
    {
        printf("输入串中有非法字符\n");
        exit(1);
    }
    B[j]=ch;
    j++;
}while(ch!='#');

l=j;/*分析串长度*/
ch=B[0];/*当前分析字符*/
A[top]='#'; A[++top]='E';/*'#','E'进栈*/
printf("步骤\t\t分析栈 \t\t剩余字符 \t\t所用产生式 \n");
do
{
    x=A[top--];/*x为当前栈顶字符*/
    printf("%d",k++);
    printf("\t\t");
}   

for(j=0;j<=5;j++)/*判断是否为终结符*/
    if(x==v1[j])
    {   
        flag=1;
        break;
    }
    if(flag==1)/*如果是终结符*/
    {
        if(x=='#')
        {
            finish=1;/*结束标记*/
            printf("acc!\n");/*接受 */
            getchar();
            getchar();
            exit(1);
        }
        if(x==ch)
        {
            print();
            print1();
            printf("%c匹配\n",ch);
            ch=B[++b];/*下一个输入字符*/
            flag=0;/*恢复标记*/
        }
        else/*出错处理*/
        {
            print();
            print1();
            printf("%c出错\n",ch);/*输出出错终结符*/
            exit(1);  
        }
    }
    else/*非终结符处理*/
    {
        for(j=0;j<=4;j++)
            if(x==v2[j])
            {
                m=j;/*行号*/
                break;
            }
        for(j=0;j<=5;j++)
            if(ch==v1[j])
            {
                n=j;/*列号*/
                break;
            }
        cha=C[m][n];
        if(cha.origin!='N')/*判断是否为空*/
        {
            print();
            print1();
            printf("%c->",cha.origin);/*输出产生式*/
            for(j=0;j<cha.length;j++)
                printf("%c",cha.array[j]);
            printf("\n");
            for(j=(cha.length-1);j>=0;j--)/*产生式逆序入栈*/
                A[++top]=cha.array[j];
            if(A[top]=='^')/*为空则不进栈*/
                top--;
        }
    }
}

void print()/*输出分析栈 */
{
    int a;/*指针*/
    for(a=0;a<=top+1;a++)
        printf("%c",A[a]);
    printf("\t\t");
}

void print1()/*输出剩余串*/
{
    int j;
    for(j=0;j<b;j++)/*输出对齐符*/
        printf(" ");
    for(j=b;j<=l;j++)
        printf("%c",B[j]);
    printf("\t\t\t");
}

// 主程序中的各变量定义
char A[20];/*分析栈*/
char B[20];/*剩余串*/
char v1[20]={'i','+','*','(',')','#'};/*终结符 */
char v2[20]={'E','G','T','S','F'};/*非终结符 */

int j=0,b=0,top=0,l;/*L为输入串长度 */
typedef struct type/*产生式类型定义 */
{
    char origin;/*大写字符 */
    char array[5];/*产生式右边字符 */
    int length;/*字符个数 */
}type;

type e,t,g,g1,s,s1,f,f1;/*结构体变量 */
type C[10][10];/*预测分析表 */

/*把文法产生式赋值结构体*/
e.origin='E';
strcpy(e.array,"TG");
t.origin='T';
strcpy(t.array,"FS");
g.origin='G';
strcpy(g.array,"+TG");
g1.origin='G';
g1.array[0]='^';
s.origin='S';
strcpy(s.array,"*FS");
s1.origin='S';
s1.array[0]='^';
f.origin='F';
strcpy(f.array,"(E)");
f1.origin='F';
f1.array[0]='i';

for(m=0;m<=4;m++)/*初始化分析表*/
    for(n=0;n<=5;n++)
        C[m][n].origin='N';/*全部赋为空*/
/*填充分析表*/
C[0][0]=e;C[0][3]=e;
C[1][1]=g;C[1][4]=g1;C[1][5]=g1;
C[2][0]=t;C[2][3]=t;
C[3][1]=s1;C[3][2]=s;C[3][4]=C[3][5]=s1;
C[4][0]=f1;C[4][3]=f;

2.3 实验内容

2.3.1 实验解决代码

根据参考代码修改如下:

do/*读入分析串*/
{
    scanf("%c",&ch);
    if ((ch!='i') &&(ch!='+') &&(ch!='*')&&(ch!='(')&&(ch!=')')&&(ch!='#'))
    {
        printf("输入串中有非法字符\n");
        exit(1);
    }
    B[j]=ch;
    j++;
}while(ch!='#');
l=j;/*分析串长度*/
ch=B[0];/*当前分析字符*/
A[top]='#'; A[++top]='E';/*'#','E'进栈*/
printf("步骤\t\t分析栈 \t\t剩余字符 \t\t所用产生式 \n");
do
{
    x=A[top--];/*x为当前栈顶字符*/
    printf("%d",k++);
    printf("\t\t");
}

for(j=0;j<=5;j++)/*判断是否为终结符*/
    if(x==v1[j])
    {   
        flag=1;
        break;
    }
    if(flag==1)/*如果是终结符*/
    {
        if(x=='#')
        {
            finish=1;/*结束标记*/
            printf("acc!\n");/*接受 */
            getchar();
            getchar();
            exit(1);
        }
        if(x==ch)
        {
            print();
            print1();
            printf("%c匹配\n",ch);
            ch=B[++b];/*下一个输入字符*/
            flag=0;/*恢复标记*/
        }
        else/*出错处理*/
        {
            print();
            print1();
            printf("%c出错\n",ch);/*输出出错终结符*/
            exit(1);
        }
    }
    else/*非绂
        for(j=0;j<=4;j++)
            if(x==v2[j])
            {
                m=j;/*行号*/
                break;
            }
        for(j=0;j<=5;j++)
            if(ch==v1[j])
            {
                n=j;/*列号*/
                break;
            }
        cha=C[m][n];
        if(cha.origin!='N')/*判断是否为空*/
        {
            print();
            print1();
            printf("%c->",cha.origin);/*输出产生式*/
            for(j=0;j<cha.length;j++)
                printf("%c",cha.array[j]);
            printf("\n");
            for(j=(cha.length-1);j>=0;j--)/*产生式逆序入栈*/
                A[++top]=cha.array[j];
            if(A[top]=='^')/*为空则不进栈*/
                top--;
        }
    }
}

void print()/*输出分析栈 */
{
    int a;/*指针*/
    for(a=0;a<=top+1;a++)
        printf("%c",A[a]);
    printf("\t\t");
}

void print1()/*输出剩余串*/
{
    int j;
    for(j=0;j<b;j++)/*输出对齐符*/
        printf(" ");
    for(j=b;j<=l;j++)
        printf("%c",B[j]);
    printf("\t\t\t");
}

// 主程序中的各变量定义
char A[20];/*分析栈*/
char B[20];/*剩余串*/
char v1[20]={'i','+','*','(',')','#'};/*终结符 */
char v2[20]={'E','G','T','S','F'};/*非绂
int j=0,b=0,top=0,l;/*L为输入串长度 */
typedef struct type/*产生式类型定义 */
{
    char origin;/*大写字符 */
    char array[5];/*产生式右边字符 */
    int length;/*字符个数 */
}type;

type e,t,g,g1,s,s1,f,f1;/*结构体变量 */
type C[10][10];/*预测分析表 */

/*把文法产生式赋值结构体*/
e.origin='E';
strcpy(e.array,"TG");
t.origin='T';
strcpy(t.array,"FS");
g.origin='G';
strcpy(g.array,"+TG");
g1.origin='G';
g1.array[0]='^';
s.origin='S';
strcpy(s.array,"*FS");
s1.origin='S';
s1.array[0]='^';
f.origin='F';
strcpy(f.array,"(E)");
f1.origin='F';
f1.array[0]='i';

for(m=0;m<=4;m++)/*初始化分析表*/
    for(n=0;n<=5;n++)
        C[m][n].origin='N';/*全部赋为空*/
/*填充分析表*/
C[0][0]=e;C[0][3]=e;
C[1][1]=g;C[1][4]=g1;C[1][5]=g1;
C[2][0]=t;C[2][3]=t;
C[3][1]=s1;C[3][2]=s;C[3][4]=C[3][5]=s1;
C[4][0]=f1;C[4][3]=f;

2.3.2 运行结果

输入正确的测试用例

i+i*i

​​

输入错误的测试用例

一>i+i

​​

输入错误的测试用例二

i+i*i)

​​

输入错误的测试用例三

i**i

​​


2.3.3 详细代码分析

1.首先,在程序的开头包含了 <stdio.h> 和 <string.h> 这两个头文件,用于提供输入输出和字符串处理的功能。

2.定义了宏 number_zjfu 和 unnumber_zjfu 分别表示终结符和非终结符的个数。

3.声明了全局变量和结构体

 stack1 和 stack2 是字符数组,分别表示分析栈和剩余串。

  1. terminal_array 是一个字符数组,存储终结符。
  2. non_terminal_array 是一个字符数组,存储非终结符。
  3. struct Production 是一个结构体类型,表示产生式,包括产生式的起始符号、右边的字符数组和字符个数。

 e、t、g、g1、s、s1、f、f1 是 struct Production 类型的结构体变量,表示相应的产生式。

  1. analyseTable 是一个二维数组,存储预测分析表的内容。

4.定义辅助变量:

  1. frist 表示输入串的指针,初始值为 0。
  2. last 表示分析栈的指针,初始值为 0。
  3. length_of_string 表示输入串的长度。
  4. userF 和 stacktop 分别表示当前处理的输入串中的字符和栈顶的字符。
  5. statue 用于表示分析状态,初始值为 0。
  6. proce 用于记录分析步骤的序号,初始值为 1。

5.定义了一系列函数的原型,包括 init()、analyse()、printStack()、printRemainString() 和 input_string()。

6.main() 函数是程序的主函数。

    1. 在 main() 函数中首先调用 init() 函数进行初始化。
    2. 接下来通过 input_string() 函数获取用户输入的串,并进行合法性判断。
    3. 将结束符号 # 和开始符号 E 分别入栈。
    4. 进入一个循环,在循环中调用 analyse() 函数进行分析,直到 statue 变量的值变为 1 表示分析完成。
    5. 分析完成后,程序结束并返回 1。

7.input_string() 函数用于获取用户输入的串。

  1. 首先打印提示信息,要求用户输入终结符。
  2. 使用 getchar() 函数逐个读取用户输入的字符,并将其存储到 stack2 数组中。
  3. 判断输入的字符是否合法,如果不是终结符则输出错误信息并返回 false。
  4. 将结束符号 # 加入到 stack2 数组中,并将输入串的
  5. 长度保存到 length_of_string 变量中,并返回 true 表示输入串合法。

8.init() 函数用于初始化产生式和预测分析表。

  1. 初始化了各个产生式的起始符号、右边字符数组和字符个数。
  2. 初始化了预测分析表 analyseTable,将非终结符对应的产生式填入表中。

9.analyse() 函数用于执行分析操作。

  1. 获取栈顶字符 stacktop 和输入串第一个字符 userF。
  2. 判断栈顶字符是否为终结符,如果是则进行终结符匹配操作。
  3. 如果栈顶字符为 #,且输入串字符也为 #,表示输入串已经全部匹配完成,打印当前分析栈和剩余串,并输出 "acc!" 表示分析成功,将 statue 设置为 1 表示分析完成。
  4. 如果栈顶字符和输入串字符相等,则进行匹配操作,将指针 frist 向后移动一位,将指针 last 向前移动一位,并将终结符标志 logo 设为 0。
  5. 如果栈顶字符和输入串字符不相等,表示匹配失败,将 statue 设置为 2 表示错误状态,打印当前分析栈和剩余串,并输出 "error" 表示错误。
  6. 如果栈顶字符不是终结符,进行查表操作。
  7. 首先根据栈顶字符找到对应的行号 row。
  8. 然后根据输入串字符找到对应的列号 column。
  9. 根据行号和列号在预测分析表 analyseTable 中找到对应的产生式 cha。
  10. 如果产生式不为空(即 cha.origin 不为 'N'),表示可以继续分析。
  11. 打印当前分析栈和剩余串。
  12. 输出产生式的左边字符 cha.origin 和右边字符数组 cha.array。
  13. 将栈顶字符出栈,根据产生式逆序将字符入栈。
  14. 如果产生式右边的第一个字符为 '^',则将其出栈。
  15. 如果产生式为空(即 cha.origin 为 'N'),表示无对应的产生式,说明输入串不符合文法规则,将 statue 设置为 2 表示错误状态,打印当前分析栈和剩余串,并输出 "error" 表示错误。

10.printStack() 函数用于打印分析栈。

  1. 首先打印当前分析步骤的序号 proce。
  2. 然后遍历分析栈数组 stack1,将栈中的字符逐个输出。

11.printRemainString() 函数用于打印剩余串。

  1. 首先打印一定数量的空格以对齐输出。
  2. 遍历剩余串数组 stack2,从指针 frist 开始输出剩余的字符。

12.在 main() 函数中进行程序的主要逻辑。

  1. 首先进行初始化操作调用 init() 函数。
  2. 使用循环获取用户输入的分析串,直到输入合法的分析串为止,调用 input_string() 函数。
  3. 将结束符 # 和起始符号 E 分别压入分析栈数组 stack1 中。
  4. 使用循环进行分析操作,直到 statue 不为 0。
  5. 调用 analyse() 函数执行分析操作。
  6. 返回 1,表示程序执行完毕。

这段程序实现了基于LL(1)分析法的语法分析器。通过使用预测分析表和栈来进行自顶向下的语法分析,并且比较栈顶符号和输入串的符号,根据预测分析表中的产生式进行匹配和规约操作,直到分析完成或出现错误。在分析过程中,输出每一步的分析栈、剩余串和所使用的产生式。


2.3.3.1 init()函数
void init(){
    e.origin='E';
    strcpy(e.array,"TG");
    e.length = 2;

    t.origin='T';
    strcpy(t.array,"FS");
    t.length = 2;

    g.origin='G';
    strcpy(g.array,"+TG");
    g.length = 3;

    g1.origin='G';
    g1.array[0]='^';
    g1.length = 1;

    s.origin='S';
    strcpy(s.array,"*FS");
    s.length = 3;

    s1.origin='S';
    s1.array[0]='^';
    s1.length = 1;

    f.origin='F';
    strcpy(f.array,"(E)");
    f.length = 3;

    f1.origin='F';
    f1.array[0]='i';
    f1.length = 1;

    for(int m=0; m<unnumber_zjfu; m++) //初始化分析表
        for(int n=0; n<number_zjfu; n++)
            analyseTable[m][n].origin='N';

    analyseTable[0][0]=e;
    analyseTable[0][3]=e;
    analyseTable[1][1]=g;
    analyseTable[1][4]=g1;
    analyseTable[1][5]=g1;
    analyseTable[2][0]=t;
    analyseTable[2][3]=t;
    analyseTable[3][1]=s1;
    analyseTable[3][2]=s;
    analyseTable[3][4]=s1;
    analyseTable[3][5]=s1;
    analyseTable[4][0]=f1;
    analyseTable[4][3]=f;
}

分析如下:

这里的init() 函数用于初始化语法分析器中的产生式和预测分析表。通过定义了一系列的结构体变量来表示产生式,每个产生式包含三个属性:origin 表示产生式的起始符号,array 表示产生式右边的字符序列,length 表示产生式右边字符序列的长度。

然后,根据文法的产生式规则,为每个结构体变量赋值。具体赋值如下:

  1. e 产生式:起始符号为 E,右边字符序列为 "TG",长度为 2。
  2. t 产生式:起始符号为 T,右边字符序列为 "FS",长度为 2。
  3. g 产生式:起始符号为 G,右边字符序列为 "+TG",长度为 3。
  4. g1 产生式:起始符号为 G,右边字符序列为 "^",长度为 1。
  5. s 产生式:起始符号为 S,右边字符序列为 "*FS",长度为 3。
  6. s1 产生式:起始符号为 S,右边字符序列为 "^",长度为 1。
  7. f 产生式:起始符号为 F,右边字符序列为 "(E)",长度为 3。
  8. f1 产生式:起始符号为 F,右边字符序列为 "i",长度为 1。

接下来,通过双重循环初始化预测分析表 analyseTable。循环变量 m 遍历非终结符数组的索引,循环变量 n 遍历终结符数组的索引。每个表格项是一个产生式结构体。将所有表格项的 origin 属性设置为 'N',表示空,再根据预测分析表中的产生式规则,为表格项赋值。具体赋值如下:

  1. analyseTable[0][0] 赋值为 e 产生式。
  2. analyseTable[0][3] 赋值为 e 产生式。
  3. analyseTable[1][1] 赋值为 g 产生式。
  4. analyseTable[1][4] 和 analyseTable[1][5] 赋值为 g1 产生式。
  5. analyseTable[2][0] 赋值为 t 产生式。
  6. analyseTable[2][3] 赋值为 t 产生式。
  7. analyseTable[3][1] 赋值为 s1 产生式。
  8. analyseTable[3][2] 赋值为s产生式。
  9. analyseTable[3][4] 和 analyseTable[3][5] 赋值为 s1 产生式。
  10. analyseTable[4][0] 赋值为 f1 产生式。
  11. analyseTable[4][3] 赋值为 f 产生式。

这样预测分析表 analyseTable 中的每个表格项都被正确赋值,表示了语法分析中的产生式规则。


2.3.3.2 analyse()函数
void analyse(){
    stacktop = stack1[last]; //获取栈顶字符
    userF = stack2[frist]; //获取用户输入串第一个字符

    int logo = 0; //终结符标志

    for(int j=0; j<number_zjfu; j++){ /*判断是否为终结符*/
        if(stacktop==terminal_array[j]){
            logo=1;
            break;
        }
    }

    if(logo==1){ //如果是终结符
        if(stacktop=='#'&&userF=='#'){   //如果该终结符是#,说明输入串已经全部匹配完成。
            printStack();
            printRemainString();
            statue=1;
            printf("acc!\n");
            return ;
        }
        
        if(stacktop==userF){ //相等则匹配
            printStack();
            printRemainString();
            printf("%c匹配\n",userF);
            frist++;
            last--;
            logo = 0;
        }
        else{
            statue = 2;
            printStack();
            printRemainString();
            printf("error"); //输出产生式
            return;
        }
    }
    else{  //查表
        int row,column;
        for(int j=0; j<unnumber_zjfu; j++){
            if(stacktop==non_terminal_array[j]){
                row = j;  //行号 
                break;
            }
        }
        
        for(int j=0; j<=number_zjfu; j++){
            if(userF==terminal_array[j]){
                column = j;  //列号
                break;
            }
        }
        
        Production cha = analyseTable[row][column];
        if(cha.origin!='N'){ //如果对应的产生式不为空,说明可以继续分析
            printStack();
            printRemainString();
            printf("%c->",cha.origin); //输出产生式
            printf("%s\n",cha.array);
            
            //如果为非终结符,则栈顶的非终结符要去掉
            last--;
            
            for(int j=(cha.length-1); j>=0; j--){ /*产生式逆序入栈*/
                last++;
                stack1[last]=cha.array[j];
            }
            
            if(stack1[last]=='^'){
                last--;
            }
        }
        else{
            //如果对应的产生式为N,则报错。
            statue = 2; //状态为报错状态
            printStack();
            printRemainString();
            printf("error"); //输出产生式
        }
    }
    return ;
}

分析如下:

这里实现了语法分析的核心部分,即根据当前的栈顶符号和输入串的首字符进行分析和匹配。代码先通过以下语句获取栈顶字符和输入串的首字符:

stacktop = stack1[last]; //获取栈顶字符
userF = stack2[frist]; //获取用户输入串第一个字符

接着通过一个循环判断栈顶字符是否为终结符:

int logo = 0; //终结符标志
for(int j=0; j<number_zjfu; j++){  /*判断是否为终结符*/
    if(stacktop==terminal_array[j]){
        logo=1;
        break;
    }
}

如果栈顶字符是终结符,执行以下逻辑:

首先,检查栈顶字符是否为终止符号 #,同时输入串的首字符也是 #。如果是,说明输入串已经全部匹配完成,打印分析栈和剩余串,设置状态为1(acc),输出 "acc!",然后函数返回。

如果栈顶字符和输入串的首字符相等,说明匹配成功,打印分析栈和剩余串,输出当前匹配的终结符号,并更新分析栈和剩余串的指针,即 frist++ 和 last--,并将终结符标志 logo 设置为0,表示不是终结符。

如果栈顶字符和输入串的首字符不相等,说明匹配失败,将状态设置为2(error),打印分析栈和剩余串,输出 "error",然后函数返回。

如果栈顶字符不是终结符,执行以下逻辑:

首先通过循环找到栈顶字符所在的非终结符的行号 row。然后通过循环找到输入串的首字符所在的终结符的列号 column。接着从预测分析表 analyseTable 中获取对应的产生式 cha,根据行号和列号索引到对应的表格项。

如果对应的产生式不为空(即 origin 字段不为 'N'),说明可以继续分析。打印分析栈和剩余串,输出产生式的左部和右部,然后更新分析栈,将产生式的右部逆序入栈。如果对应的产生式为空(即 origin 字段为 'N'),说明发生错误。将状态设置为2(error),打印分析栈和剩余串,输出 "error"。

最后,函数返回,继续下一步的分析过程。


2.4 实验心得

通过本次实验,我实现了LL(1)分析法进行语法分析,并认识到LL(1)分析法利用预测分析表和栈来进行符号匹配和产生式的选择,从而推导出输入串的语法结构。

首先,我了解到LL(1)分析法的核心是构建预测分析表。预测分析表由非终结符和终结符构成,通过预测分析表我们可以根据当前的栈顶符号和输入串的首符号,快速确定应该选择的产生式,从而进行语法推导。在实验中,我通过定义非终结符和终结符的数组以及预测分析表的初始化,构建了一个完整的预测分析表。

其次,我认识到LL(1)分析法对文法的要求比较严格,文法必须满足LL(1)文法的条件。LL(1)文法要求每个非终结符的每个产生式的选择集与其他产生式的选择集没有交集,这样才能保证在分析过程中不会出现二义性和回溯。在实验中,我针对给定的文法,仔细检查了每个非终结符的产生式,并根据LL(1)文法的条件进行了调整和修改,确保文法满足LL(1)的要求。

在编写代码的过程中,我深入理解了LL(1)分析法的工作原理。通过构建函数analyse()的代码,我实现了循环的语法分析过程。在每次循环中,根据栈顶字符和输入串的首字符进行匹配,并根据预测分析表选择相应的产生式。通过不断地匹配和产生式的选择,逐步推导出输入串的语法结构。

通过实验,我对LL(1)分析法的应用有了更深刻的理解。我意识到LL(1)分析法在编译原理中的重要性,它可以帮助构建抽象语法树或生成中间代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1821713.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Stable Diffusion【光影文字】:绚丽光影,文字与城市夜景的光影之约

今天我们我们结合城市夜景背景来看一下光影文字的效果&#xff0c;我们先来看一下效果图。 一. 字融城市夜景制作光影文字方法 【第一步】&#xff1a;制作底图这里制作底图使用黑底白字。我们使用美图秀秀制作一个"小梁子"字的底图。 字体&#xff1a;默认字体 图…

LLM 中什么是Prompts?如何使用LangChain 快速实现Prompts 一

LLM 中什么是Prompts&#xff1f;如何使用LangChain 快速实现Prompts 一 Prompt是一种基于自然语言处理的交互方式&#xff0c;它通过机器对自然语言的解析&#xff0c;实现用户与机器之间的沟通。 Prompt主要实现方式是通过建立相应的语料库和语义解析模型&#xff0c;来将自…

对于毕业季拨穗礼上的奇怪举动与表情包,您怎么看?

对于毕业季拨穗礼上的奇怪举动与表情包&#xff0c;您怎么看&#xff1f; 拨穗代表稻穗或麦穗成熟。什么叫拨穗礼&#xff1f;就是把你头上学士帽的流苏从右边换到左边的动作。它象征着你已学有所成,可以展翅高飞了。 又到毕业季&#xff0c;各大高校的校园里弥漫着别离与期待…

评论员稿:逯伟军,年轻人为何为这样的“新中式”买单?

继老钱风、美拉德、Y2K等流行趋势之后&#xff0c;“新中式”毫无争议地荣登当下最为热门的穿搭趋势之列。 不管是出于对传统文化的热爱&#xff0c;还是仅仅被中式美学的设计所吸引&#xff0c;总之从明星到时尚博主&#xff0c;再到普通消费者&#xff0c;新中式风格在社交平…

Talk|新加坡国立大学贾鑫宇:适用于高自由度机器人的运动控制器

本期为TechBeat人工智能社区第600期线上Talk。 北京时间6月13日(周四)20:00&#xff0c;新加坡国立大学博士生—贾鑫宇的Talk已经准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “适用于高自由度机器人的运动控制器”&#xff0c;向大家系统地介绍了如何通…

期货到底难在哪里?

第一难&#xff1a;使用杠杠&#xff0c;杠杠放大的其实是你性格、天赋和技能上的弱点&#xff0c;同时相应缩小你这三个方面的优点&#xff1b;第二难&#xff1a;双向交易。如果只能做多&#xff0c;理论上你每次交易将有50%的概率盈利。现在既能做多又能做空&#xff0c;只剩…

最前端|还在焦虑网页内容无法保存?一文教你轻松给网页拍照

目录 一、需求背景 二、实现策略 三、基础实现 四、功能完善 五、效果演示 六、总结 一、需求背景 网页作为一种关键的信息传递媒介&#xff0c;承载着丰富多样的内容形态&#xff0c;包括但不限于文本、图像、表格及多媒体元素。然而&#xff0c;网页内容的动态性和不稳定…

郑州企业资信评价资质:工程咨询单位专业资信申报材料清单

在郑州企业申请工程咨询单位专业资信评价资质时&#xff0c;需要准备一系列详细的申报材料。以下是根据参考文章中的相关数字和信息&#xff0c;清晰、分点表示和归纳的工程咨询单位专业资信申报材料清单&#xff1a; 一、基础材料 企业法人营业执照&#xff1a;提供企业法人营…

微信小程序投票系统(包含微信小程序端)

&#x1f4f1;微信投票小程序&#xff1a;轻松发起&#xff0c;快速统计 一、引言 在数字化时代&#xff0c;微信作为我们日常生活中不可或缺的社交工具&#xff0c;不仅为我们提供了沟通交流的平台&#xff0c;还衍生出了许多实用的小程序。其中&#xff0c;微信投票小程序凭…

《现代通信原理与技术》--数字信号的最佳接收实验报告

《现代通信原理与技术》 数字信号的最佳接收实验报告 实 验&#xff1a;数字信号的最佳接收实验报告 目录 摘要......................................................................................................3 引言........................................…

嵌入式系统复习(一)

第一章 嵌入式系统的定义、特点 嵌入式系统是以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软件硬件可裁剪&#xff0c;适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。 特点&#xff1a;嵌入性 专用性 计算机系统 嵌入式系统典型组成…

线代老师大PK,这四位胜出!

说实话&#xff0c;线代真的别乱跟老师 因为每个老师讲课适用的人群不一样&#xff0c;比如都说李永乐老师线代讲的好&#xff0c;但是我去听完发现&#xff0c;李永乐老师的线代讲的虽然好&#xff0c;但是对于零基础或者基础不好的考生来说&#xff0c;真的有点不友好&#…

Python开发者必备:Python多平台消息推送工具

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒🚀 安装📝 使用方法📝 同步发送消息📝 异步发送消息📝 使用配置文件发送消息📝 示例配置文件⚓️ 相关链接 ⚓️📖 介绍 📖 你是否需要一个强大且灵活的工具来实现多平台消息推送?那么你一定不能…

手机直播不用麦克风可以吗?一文看懂无线麦克风哪个好

市面上对于无线麦克风的需求有增无减&#xff0c;原因是直播、短视频行业火爆&#xff0c;许多人都开始加入这一行业&#xff0c;不过对于麦克风这类产品的疑问也越来越多。例如&#xff1a;无线麦克风怎么选&#xff1f;实不实用&#xff1f;手机直播不用麦克风可以吗&#xf…

服务器数据恢复—EMC Isilon存储中被误删的虚拟机数据恢复案例

服务器存储数据恢复环境&#xff1a; EMC Isilon S200集群存储&#xff0c;共三个节点&#xff0c;每节点配置12块SATA硬盘。 服务器存储故障&#xff1a; 工作人员误操作删除虚拟机&#xff0c;虚拟机中数据包括数据库、MP4、AS、TS类型的视频文件等。需要恢复数据的虚拟机通…

雪球产品学习,大雪球vs小雪球

part1 大雪球 一、 雪球产品的要素 1.1 定义:固收+期权的结合 具备敲入,敲出的Barrier put (看跌=认沽) 期权的买方:产品发行方 期权的卖方:雪球产品购买者 1.2 七要素 挂钩标的,锁定期,敲入价格,敲出价格,产品期限,观察频率,产品报价 1.3 挂钩标的 个股or指…

AI类人工智能产品经理的丛林法则

AI是大家都很关注的领域&#xff0c;然而对于大部分想要入行的同学来讲&#xff0c;AI的算法技术门槛相对较高&#xff0c;让很多空有热血但是缺少数学背景的同学望而却步。不知道什么时候&#xff0c;可能是“人人都是产品经理”这个论调的影响&#xff0c;产品经理这个岗位逐…

剖析框架代码结构的系统方法(下)

当面对Dubbo、Spring Cloud、Mybatis等开源框架时,我们可以采用一定的系统性的方法来快速把握它们的代码结构。这些系统方法包括对架构演进过程、核心执行流程、基础架构组成和可扩展性设计等维度的讨论。 在上一讲中,我们已经讨论了架构演进过程和核心执行流程这两个系统方法…

【实践功能记录6】表格列悬浮展示tooltip信息

需求描述&#xff1a; 鼠标悬浮在表格的IP字段上时&#xff0c;使用tooltip展示IP信息&#xff0c;如图&#xff1a; 1.封装根据IP展示信息的组件 请求接口获取IP信息&#xff0c;注意请求接口时防抖 <!-- 根据IP展示资产信息 --> <template><div><el-…

vue-i18n使用步骤详解(含完整操作步骤)

开篇 下面是从创建vue项目开始&#xff0c;完整使用i18n实现国际化功能的步骤&#xff0c;希望对您有所帮助。 完整步骤 创建项目 创建项目&#xff0c;并在创建项目的时候选择vuex,router 选择3.x版本 后面随意选即可&#xff0c;下面是完整的代码结构 安装vue-i18n,并封装…