自顶向下的L25编译器C++实现
本文最后更新于 16 天前,其中的信息可能已经有所发展或是发生改变。

本项目基于L25文法,利用C++实现了一个编译器,包含词法分析、语法分析、出错处理、代码生成和解释程序。L25文法是简单的编程语言文法,本项目在其基础定义上扩展了字符串以及指针的实现。具体见下面给出的扩展后的L25文法的定义:

program:

program  ::= 'program' ident '{' func_def* 'main' '{' stmt_list '}' '}'

no references


func_def:

func_def ::= 'func' ident '(' param_list? ')' '{' stmt_list 'return' expr ';' '}'

referenced by:


param_list:

         ::= ident ( ',' ident )*

referenced by:


stmt_list:

         ::= ( stmt ';' )+

referenced by:


stmt:

stmt     ::= declare_stmt
           | assign_stmt
           | if_stmt
           | while_stmt
           | input_stmt
           | output_stmt
           | func_call

referenced by:


declare_stmt:

         ::= 'let' '@'? ident ( '=' expr )?
           | 'str' '@'? ident ( '=' str )?

referenced by:


assign_stmt:

         ::= '@'? ident '=' expr

referenced by:


if_stmt:

if_stmt  ::= 'if' '(' bool_expr ')' '{' stmt_list '}' ( 'else' '{' stmt_list '}' )?

referenced by:


while_stmt:

         ::= 'while' '(' bool_expr ')' '{' stmt_list '}'

referenced by:


func_call:

         ::= ident '(' arg_list? ')'

referenced by:


arg_list:

arg_list ::= expr ( ',' expr )*

referenced by:


input_stmt:

         ::= 'input' '(' ident ( ',' ident )* ')'

referenced by:


output_stmt:

         ::= 'output' '(' expr ( ',' expr )* ')'

referenced by:


bool_expr:

         ::= expr ( '==' | '!=' | '<' | '<=' | '>' | '>=' ) expr

referenced by:


expr:

expr     ::= str_expr
           | arith_expr

referenced by:


arith_expr:

         ::= ( '+' | '-' )? term ( ( '+' | '-' ) term )*

referenced by:


term:

term     ::= factor ( ( '*' | '/' ) factor )*

referenced by:


factor:

factor   ::= ( '@' | '&' )? ident
           | number
           | str
           | '(' expr ')'
           | func_call

referenced by:


ident:

ident    ::= letter ( letter | digit )*

referenced by:


number:

number   ::= digit+

referenced by:


letter:

letter   ::= 'a'
           | 'b'
           | 'c'
           | 'd'
           | 'e'
           | 'f'
           | 'g'
           | 'h'
           | 'i'
           | 'j'
           | 'k'
           | 'l'
           | 'm'
           | 'n'
           | 'o'
           | 'p'
           | 'q'
           | 'r'
           | 's'
           | 't'
           | 'u'
           | 'v'
           | 'w'
           | 'x'
           | 'y'
           | 'z'
           | 'A'
           | 'B'
           | 'C'
           | 'D'
           | 'E'
           | 'F'
           | 'G'
           | 'H'
           | 'I'
           | 'J'
           | 'K'
           | 'L'
           | 'M'
           | 'N'
           | 'O'
           | 'P'
           | 'Q'
           | 'R'
           | 'S'
           | 'T'
           | 'U'
           | 'V'
           | 'W'
           | 'X'
           | 'Y'
           | 'Z'

referenced by:


digit:

digit    ::= '0'
           | '1'
           | '2'
           | '3'
           | '4'
           | '5'
           | '6'
           | '7'
           | '8'
           | '9'

referenced by:


str_expr:

str_expr ::= str ( '*' ( number | arith_expr ) | '+' ( number | str | arith_expr ) )

referenced by:


str:

str      ::= '"' str_char* '"'

referenced by:


str_char:

str_char ::= '除了\和"之外的任何 ASCII 字符'
           | escape_sequence

referenced by:


escape_sequence:

         ::= '\' ( '"' | '\' | 'n' | 't' )

referenced by:



关于L25文法有一些注意事项如下:

  1. func 体里最后必须有 return;
  2. main或普通语句块里不能使用 return;
  3. 每个stmt_list 至少有一条语句。

示例代码(求最小公倍数):

program LCMpro{
    func gcd(a, b) {
        let result;
        if (b == 0) {
            result = a;
        } else {
            result = gcd(b, a - (a/b)*b);
        };
        return result;
    }
    func lcm(a, b) {
        let g = gcd(a, b);
        let result = (a * b) / g;
        return result;
    }
    
    main {
        let a;
        let b;
        
        output("Number1\n");
        input(a);
        output("Number2\n");
        input(b);
        
        if (a > 0) {
            if(b>0){
                let result = lcm(a, b);
                output("LCM:\n");
                output(result);
            };
        } else {
            output("Integer only!");
        };
    }
}

关于保留字的说明:

根据上面L25文法的EBNF和铁路图描述,L25语言包含11个保留字,分别是program, func, main, return, let, if, else, while, input, output, str;支持支持字符串连接运算以及字符串和整型之间的运算,例如str a = "hello world";let b = 3;str c = a + b;str d = a*b; str e = a + d;

关于字符串的内存管理的说明:

对于字符串的内存管理,此编译器采用C++的数组虚拟一个数据栈,数据栈的大小为5000,其中0到999为常规数据栈,1000到1999为字符串常量的字面量值,2000到4999为运行时字符串栈,保存运行时程序产生的字符串字面量的值。字符串变量保存在常规数据栈中,存储的值实际是字符串所在的地址。基于这种内存管理机制,可以实现指针的支持。

关于指针的说明:

此编译器扩展了指针,指针声明需要在声明符号后面加 @,例如let@ p = 1;,同时允许取地址值赋值给指针,取地址符号为 &,例如p = &a;。允许指针的加减运算,例如p = p + 1;。此时p指向的是下一个元素所在的地址,通过@p,可以取出对应的值。

代码结构:

该编译器仿照java版本的P/L0编译器,按照词法分析、语法分析、代码生成和执行的编译流程,主要由以下几个核心模块组成:

1. 编译器整合核心 ( Compiler.hpp , Compiler.cpp ) 编译器的中央协调器,用于实现编译器的实例,集成并管理其他各个模块。在构造函数中初始化词法分析器 ( Scanner )、符号表 ( Table )、语法分析器 ( Parser ) 和解释器 ( Interpreter )并调用其中编译时需要用到的函数或方法。

2. 词法分析器 ( Scanner.hpp , Scanner.cpp ) 读取L25源程序文本,将其分解成一系列的词法单元(tokens/symbols)。

3. 符号表 ( table.hpp , table.cpp ) 存储和管理在源程序中声明的标识符(如变量、函数、字符串等)的信息。

4. 语法分析器 ( Parser.hpp , Parser.cpp ) 根据L25语言的语法规则,分析词法分析器提供的词法单元序列,检查语法正确性,并在此过程中生成中间代码(P-code)。采用递归下降的解析策略。包含多个 parse<NonTerminal>() 方法,对应语法规则中的非终结符(例如 parseProgram , parseStatementList , parseExpression )。使用 SymSet.hpp 中定义的 SymSet 类来辅助进行语法分析,例如用于错误恢复或预测分析中的FIRST/FOLLOW集。

5. 解释器 ( Interpreter.hpp , Interpreter.cpp )

执行由语法分析器生成的P-code指令。定义P-code指令的操作码(如 LIT , OPR , LOD , STO , CAL , JMP , JPC , WRT , RED , HLT 等),后文会对指令做详细说明。

6. 辅助模块

Symbol.h : 定义了 Symbol 枚举类,列出了L25语言所有的词法单元类型。

SymSet.hpp : 提供 SymSet 类,用于表示符号的集合,主要被语法分析器用于语法制导翻译和错误恢复。

编译器使用的P-code指令及其含义

指令格式含义
LITLIT 0 a常量值 a 推入栈顶,用于加载整数常量和字符串地址。
LODLOD l a变量的值推入栈顶。该变量位于层差 l,地址为 a。用于读取变量的值。
STOSTO l a将栈顶的值存入变量中并出栈。该变量存储于层差 l,地址为 a。
CALCAL l a调用函数。a 是函数入口地址,l 是层差。
INTINT 0 a在栈上为函数分配/释放空间。a 为正数时分配,为负数时释放。
JMPJMP 0 a无条件跳转到地址 a。
JPCJPC 0 a条件跳转。当栈顶值为 0 (false) 时,跳转到地址 a 并出栈。
LDALDA l a加载地址。将变量的地址推入栈顶,用于 &v 操作。
LDILDI 0 0间接加载。将栈顶值作为地址,取出该地址处的值放入栈顶,用于 @p 取值。
STISTI 0 0间接存储。从栈顶弹出一个值和地址,将值存入该地址,用于 @p = … 赋值。
REDRED 0 0读取输入 ( input )。从输入流读取一个值,并将其推入栈顶。
WRTWRT 0 0写输出 ( output )。弹出栈顶值并将其输出到输出流。
HLTHLT 0 0停机。程序正常执行结束。

OPR (操作) 指令

OPR 指令用于执行算术、逻辑和特定的字符串运算。

a值含义
0函数返回 ( return )。
1对栈顶值取负 (一元负号 – )。
2对栈顶的两个元素进行加法 ( + )并将结果存入栈顶。
3弹出栈顶两个元素,用前一个被压入的元素减去后一个被压入的元素,并将结果存入栈顶。 ( – )。
4对栈顶的两个元素进行乘法 ( * )并存入栈顶。
5前一个元素被压入栈顶的元素除以( / )后一个被压入的元素,将结果存入栈顶。包含除零检查。
8等于 ( == ),判断两个栈顶元素是否相等。
9不等于 ( != ),判断两个栈顶元素是否不等。
10小于 ( < ),判断前一个入栈的元素是否小于后一个入栈的元素。
11大于等于 ( >= ),判断前一个入栈的元素是否大于等于后一个入栈的元素。
12大于 ( > ),判断前一个入栈的元素是否大于后一个入栈的元素。
13小于等于 ( <= ),判断前一个入栈的元素是否小于等于后一个入栈的元素。
14带换行的输出,与 WRT 功能相同,但在输出后会自动添加一个换行符。(保留原P/L0指令设计,未使用)
15输出换行,输出一个换行符。(保留原P/L0指令设计,未使用)
16读取:与 RED 功能完全相同,从标准输入读取一个整数并压栈。(保留原P/L0指令设计,未使用)
17字符串重复 ( str * num ),将字符串重复 num 次并返回结果字符串。
18字符串连接 ( str1 + str2 ),连接两个字符串。
19字符串与数字连接 ( str + num ),连接字符串和数字。

编译器错误号及含义说明:

错误号错误含义触发条件出现位置
1嵌套层级过深函数或块的嵌套层级超过最大限制(3)parseProgram()
4期望标识符在需要标识符的位置遇到其他符号程序名、函数名、参数、变量声明等
5期望分号语句结束时缺少分号语句列表、函数返回语句
11未声明的标识符使用了未在符号表中声明的标识符变量引用、函数调用等
12不能对该标识符赋值试图对常量或函数赋值赋值语句、输入语句
13期望 ‘}’代码块未正确关闭程序体、函数体、if/while语句
16期望关系操作符条件表达式中缺少比较运算符parseCondition()
17期望 ‘{‘需要左大括号开始代码块各种代码块开始处
18期望 ‘program’ 关键字程序开始时没有program关键字parse() 函数开始
19期望 ‘main’函数定义后没有找到main函数parseProgram()
20期望 ‘return’函数体结束时没有return语句parseFunctionDef()
21期望 ‘(‘函数定义/调用或条件语句缺少左括号函数、if/while、输入输出语句
22期望 ‘)’缺少右括号匹配左括号函数、条件语句、表达式等
23非法语句遇到不支持的语句类型parseStatement() parseStatementList()
24非法的因子表达式因子不合法parseFactor()
25不是函数试图调用非函数类型的标识符函数调用解析
26期望字符串字符串表达式中遇到非字符串类型parseStringExpression()
27期望 ‘=’赋值语句中缺少赋值操作符变量赋值、字符串赋值
28不能把地址赋给非指针变量将取地址结果赋给普通变量变量/字符串声明初始化
29试图解引用非指针变量对非指针类型使用解引用操作符@parseAssignment()

出错处理机制:

错误计数: 使用静态变量 errorCount 统计错误数量

错误限制: 超过 maxerr 限制时终止编译

错误定位: 输出格式为 **空格^错误号 ,精确定位错误位置

错误恢复: 遇到错误首先会报告错误,然后进入一个循环,不断地调用 nextSym() 来丢弃(跳过) 当前及后续的符号。这个跳过过程会一直持续,直到找到一个安全的“同步符号”为止。

项目地址:

https://github.com/Jardasvai/L25Compiler

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇