Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1:EBNF和递归下降文法

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1:EBNF和递归下降文法

作者头像
云微
发布于 2023-02-11 01:58:22
发布于 2023-02-11 01:58:22
57100
代码可运行
举报
运行总次数:0
代码可运行

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1:EBNF和递归下降文法

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(3)- 词法分析

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1:EBNF和递归下降文法

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2: tryC的语法分析实现

用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(6)- 语义分析:符号表和变量、函数

项目github地址及源码:

https://github.com/yunwei37/tryC

这一章开始进入解释器的核心部分: 语法分析器;

我们来看看两个概念,EBNF和递归下降文法,以及如何用这两个方法来计算tryC中的表达式。

基本概念

就像之前所说的那样,语法分析指将词法分析得到的标记流(token)进行分析,组成事先定义好的有意义的语句。那么如何完成这样一个工作呢?我们可以借助一个叫“BNF”的数学工具。

BNF与上下文无关文法

Backus-Naur符号(就是众所周知的BNF或Backus-Naur Form)是描述语言的形式化的数学方法,由John Backus (也许是Peter Naur)开发,最早用于描述Algol 60编程语言的语法。

BNF类似一种数学游戏:从一个符号开始(叫做起始标志,实例中常用S表示),然后给出替换前面符号的规则。

BNF语法定义的语言是一个字符串集合,可以按照下述规则书写,这些规则叫做书写规范(产生式规则),例如一个四则运算表达式可以表示为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
exp -> exp op exp | ( exp ) | number
op -> + | - | * | /

其中’|'用于表示可选择的不同项,"->"用于表示推导规则,从产生式左边的符号可以推导出产生式右边的符号;

要解析一个表达式,我们可以完成这样一个替换:对于

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(3+2)*4

可以替换为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    exp => exp op exp => exp * number
        => ( exp ) * number
        => ( exp op exp ) * number
        => ( number + number ) * number

其中我们把能够被替换的符号叫做非终结符,不能被替换的叫做终结符。

上下文无关文法就是说,这个文法中所有的产生式左边只有一个非终结符,就像上面写的那个文法一样。通常我们在编译器构建中使用的都是上下文无关文法。

EBNF

EBNF是基本巴科斯范式(BNF)元语法符号表示法的一种扩展,主要对BNF中常见的两种情况,即重复项和可选项添加了相应的语法规则,如用方括号" … "

表示可选部分,用花括号"{ … }"表示重复出现的部分,如上面那个文法可以改写为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
exp -> exp { op exp } | ( exp ) | number
op -> + | - | * | /

又比如对于if语句可以写成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if-stmt -> if ( exp ) statement; [ else statement; ]

递归下降文法

递归下降分析法也很简单,就是用函数的调用来模拟BNF的替换过程,我们只需要为每个非终结符定义一个分解函数,它就能从起始非终结符开始,不断地调用非终结符的分解函数,不断地对非终结符进行分解,直到匹配输入的终结符。

当然,递归下降分析并不是对于所有的文法都能正常使用的,例如经典的左递归问题:比如这样一个文法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
exp -> exp { op exp } | ( exp ) | number
op -> + | - | * | /

对于exp的替换需要调用exp的分解函数,而exp的分解函数一进来就调用它自身(即最左边的符号),就会导致无限递归。这时就需要对文法进行改造。

实际上,EBNF文法就是为了映射递归下降分析法的具体程序实现而设计的,因此我们这里就用EBNF文法来实现递归下降分析。

来看看怎样用递归下降文法计算tryC中的表达式

上面说了一大堆,现在看看实际的计算表达式的实现是怎样的呢

算术表达式

tryC中需要计算四则运算表达式的EBNF文法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
exp -> term { addop term }
term -> factor { mulop factor }
factor -> number | ( exp )
addop -> + | -
mulop -> * | /

这里对文法进行了一定的改造,让它能够正确表达四则运算的优先级,同时避免了左递归的问题,具体可以自己试着验证一下。

tryC中算术表达式具体的代码实现(就是上述文法直接转换过来的啦):

(在下一篇文章中还会提及表达式中对变量的处理过程)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
double term() {
    double temp = factor();
    while (token == '*' || token == '/') {
        if (token == '*') {
            match('*');
            temp *= factor();
        }
        else {
            match('/');
            temp /= factor();
        }
    }
    return temp;
}

double factor() {
    double temp = 0;
    if (token == '(') {
        match('(');
        temp = expression();
        match(')');
    }
    else if(token == Num ||  token == Char){
        temp = token_val.val;
        match(Num);
    }
    else if (token == Sym) {
        temp = token_val.ptr->value;
        match(Sym);
    }
    else if (token == FuncSym) {
        return function();
    }
    else if (token == ArraySym) {
        symbol* ptr = token_val.ptr;
        match(ArraySym);
        match('[');
        int index = (int)expression();
        if (index >= 0 && index < ptr->value) {
            temp = ptr->pointer.list[index].value;
        }
        match(']');
    }
    return temp;
}

double expression() {
    double temp = term();
    while (token == '+' || token == '-') {
        if (token == '+') {
            match('+');
            temp += term();
        }
        else {
            match('-');
            temp -= term();
        }
    }
    return temp;
}

布尔表达式

tryC中同样设计了布尔表达式:

tryC中需要计算四则运算表达式的EBNF文法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
boolOR = boolAND { '||' boolAND }
boolAND = boolexp { '||' boolexp }
boolexp -> exp boolop exp | ( boolOR ) | !boolexp
boolop -> > | < | >= | <= | ==

代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int boolexp() {
    if (token == '(') {
        match('(');
        int result = boolOR();
        match(')');
        return result;
    }
    else if (token == '!') {
        match('!');
        return boolexp();
    }
    double temp = expression();
    if (token == '>') {
        match('>');
        return temp > expression();
    }
    else if (token == '<') {
        match('<');
        return temp < expression();
    }
    else if (token == GreatEqual) {
        match(GreatEqual);
        return temp >= expression();
    }
    else if (token == LessEqual) {
        match(LessEqual);
        return temp <= expression();
    }
    else if (token == Equal) {
        match(Equal);
        return temp == expression();
    }
    return 0;
}

int boolAND() {
    int val = boolexp();           
    while (token == AND) {
        match(AND);
        if (val == 0)    return 0;         // short cut
        val = val & boolexp();
        if (val == 0) return 0;
    }
    return val;
}

int boolOR() {
    int val = boolAND();
    while (token == OR) {
        match(OR);
        if (val == 1)    return 1;         // short cut
        val = val | boolAND();
    }
    return val;
}

一些重要概念

  • 终结符/非终结符
  • BNF/EBNF
  • 上下文无关文法
  • 递归下降法

可对照源码查看(如果觉得写得还行麻烦您帮我点个star哦)

https://github.com/yunwei37/tryC

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2: tryC的语法分析实现
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(3)- 词法分析 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1:EBNF和递归下降文法 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2: tryC的语法分析实现 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(6)- 语义分析:符号表和变量、函数
云微
2023/02/11
3710
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(3)- 词法分析 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1:EBNF和递归下降文法 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2: tryC的语法分析实现 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(6)- 语义分析:符号表和变量、函数
云微
2023/02/24
6340
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1
我们来看看两个概念,EBNF和递归下降文法,以及如何用这两个方法来计算tryC中的表达式。
云微
2020/06/05
1.8K0
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2
布尔表达式和算术表达式的代码之前已经讲过了,这里看看statement的实现,以及如何在语法分析的同时解释执行:
云微
2020/06/05
8460
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(3)- 词法分析
帮编译器执行词法分析阶段的模块,就叫词法分析器啦。词法分析器能够对源码字符串做预处理,以减少语法分析器的复杂程度。
云微
2020/06/05
1.4K0
从编译原理看一个解释器的实现
『设计模式』中有一个模式可以解释特定的语法规则,它就是解释器模式(Interpreter Pattern)。不同于常见的策略模式或者是工厂模式,解释器模式在.NET或者JDK中并不常见,而且在业务上也很少会去解释特定的语法,所以它并不被广泛使用。一个解释器可大可小,大可以是复杂的编译器,小也可以是一个简单的字符串解析,但本质上它们都是对特定的语法做出合理的解释。 解释器在游戏领域的应用 虽然解释器模式很少使用,但在在游戏开发中,还是很常见的。比如你在战斗时,普通攻击和魔法攻击一定会产生不同的伤害,游戏设计
用户1161731
2018/03/28
2.2K0
从编译原理看一个解释器的实现
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(3)- 词法分析 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1:EBNF和递归下降文法 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2: tryC的语法分析实现 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(6)- 语义分析:符号表和变量、函数
云微
2023/02/11
5060
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(6)- 语义分析
这一部分,我们再回过头来看看变量、函数是怎样存储和处理的、以及符号表是怎样构建的。
云微
2020/06/05
1.2K0
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计
通常我们说的 “编译器” 是一种计算机程序,负责把一种编程语言编写的源码转换成另外一种计算机代码,后者往往是以二进制的形式被称为目标代码(object code)。这个转换的过程通常的目的是生成可执行的程序。
云微
2020/06/05
1.7K0
编译原理初学者入门指南
作者:pixelcao,腾讯 IEG 后台开发工程师 一、引子 最近的工作需要用表达式做一些参数的配置,然后发现大脑一片空白,在 Google 里试了几个关键词(起初搜了下“符号引擎”,发现根本不是我想要的)之后,明白过来自己应该是需要补一些编译原理的知识了。在掉了两晚上头发之后,决定整理一下自己的知识网络。 要解析的表达式大概长这个样子: avg(teams[*].players.attributes[skill])*rules[latency].maxLatency 正则表达式是个办法,但不是最优
腾讯技术工程官方号
2021/01/21
2.5K0
编译入门 - 从零实现中文计算器
“如果你不知道编译器咋工作的你就不知道电脑是咋工作的。” -- STEVE YEGGE
羽月
2022/10/08
8200
编译入门 - 从零实现中文计算器
手写一个解析器
作者:jolamjiang,腾讯 WXG 前端开发工程师 前言 最近工作中有一些同学在做一些效能工具的时候遇到需要写一门领域相关语言(DSL)及其解析器的场景,笔者恰好有相关的经验向大家指一下北。 首先请问一下大家有没有想过这个功能怎么做? 点击播放视频 本文将围绕如何实现类似于 Excel 中 =C1+C2+"123" 这样子的表达式的功能这一例子,在不需要编译原理的相关知识的前提下,用写正则表达式作为类比,借助一个工具库,讲述实现一个领域相关语言的解析器的一般步骤,让你能够快速实现一个解析器。
腾讯技术工程官方号
2020/05/27
1.3K0
懂前端的你也可以轻松定义自己业务的DSL
jison是一个 JavaScript 编写的解析器生成器,可以用来生成自定义的编程语言解析器。它的令人兴奋的点在于,它允许开发人员使用 JavaScript 语言来定义语法规则,然后将其转换为解析器,从而支持自定义的编程语言。
老码小张
2023/03/12
2.6K0
懂前端的你也可以轻松定义自己业务的DSL
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言
这一系列教程希望面向初学者,使用c语言手工实现一个简单的解释器来玩,不需要您掌握除了c语言以外的其他前置知识,也不需要您学习过编译原理的相关知识(当然如果能对简单的数据结构有所了解的话会更好,比如树、栈等)。
云微
2020/06/05
1.5K0
解释器模式 Interpreter 行为型 设计模式(十九)
如果形势变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无穷种组合方式
noteless
2018/12/26
5640
编译原理词法分析程序c语言_编译器常用的语法分析方法
前面已经介绍了编译器的预处理,词法分析,词法分析器的实现,也在其中说到了语法分析的任务和过程。
全栈程序员站长
2022/11/16
7870
上下文无关文法产生的语言都可以用正则文法来描述_c语言结构体默认值
正则表达式只能使用终结符(字母表中的字符),因而很容易变得复杂又难懂,实际中,经常使用正则描述,正则描述允许使用非终结符定义表达式,很像EBNF,但是它限制在未完全定义之前,不能使用非终结符,也就是说不允许递归或自嵌套。
全栈程序员站长
2022/11/01
1.1K0
解释器模式
本科软件工程专业有这么一门课叫《编译原理》,课程内容已经忘了七七八八,但尤为清楚的是上机大作业是拷贝的,课程分数92。
mingmingcome
2023/05/26
3510
解释器模式
65.精读《手写 SQL 编译器 - 文法介绍》
我们将一块语法规则称为 产生式,使用 “Left → Right” 表示任意产生式,用 “Left => Right” 表示产生式的推导过程,比如对于产生式:
黄子毅
2022/03/14
5910
理解递归下降分析和parsec应用
本文将会从上下文无关文法开始介绍,从使用 BNF 描述语法到理解递归下降分析思想,最后实现一个简单的 html 解析器收尾。本文的亮点是使用 typescript 编写组合子编译器,对于前端开发某些特定领域会有重要意义和价值。同时本文注重实用价值,配合简短 js 代码示例来帮助理解。
玖柒的小窝
2021/10/31
1.8K0
理解递归下降分析和parsec应用
推荐阅读
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2: tryC的语法分析实现
3710
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计
6340
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(4)- 语法分析1
1.8K0
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2
8460
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(3)- 词法分析
1.4K0
从编译原理看一个解释器的实现
2.2K0
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言
5060
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(6)- 语义分析
1.2K0
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(2)- 简介和设计
1.7K0
编译原理初学者入门指南
2.5K0
编译入门 - 从零实现中文计算器
8200
手写一个解析器
1.3K0
懂前端的你也可以轻松定义自己业务的DSL
2.6K0
用c语言手搓一个500+行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言
1.5K0
解释器模式 Interpreter 行为型 设计模式(十九)
5640
编译原理词法分析程序c语言_编译器常用的语法分析方法
7870
上下文无关文法产生的语言都可以用正则文法来描述_c语言结构体默认值
1.1K0
解释器模式
3510
65.精读《手写 SQL 编译器 - 文法介绍》
5910
理解递归下降分析和parsec应用
1.8K0
相关推荐
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(5)- 语法分析2: tryC的语法分析实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验