学习编译器原理:从解析器看程序设计


提到编程,我们会马上想到一些通用的编程语言,比如CC++JavaPythonGo等。但是,对于绝大部分软件开发人员来说,不会从零开始设计一门通用的编程语言;更多的情况是设计一门在某一个业务领域使用的语言,也就是我们通常所说的DSL(领域特定语言,Domain Specific Language)。

麻雀虽小,五脏俱全。虽然DSL范围受限,实现相对简单,但是其原理跟通用的编程语言并无本质上的差异。学习编程语言的设计原理,可以帮助我们更好地设计一门DSL,用于解决某个特定领域的问题。

本文尝试介绍语言设计的基本原理,尤其是与Parser相关的内容。利用这些知识,可以帮助我们解决DSL设计过程中的 识别 问题,为后续的 解释执行 等环节做好准备。

语言设计的全貌

一门编程语言,就是所有合法的语句的集合。使用编程语言编写的程序,在形式上是一连串的字符序列;语言设计的工作,就是将这些字符串最终转化为可以在某一平台上执行的二进制文件,或者是将字符串转为一种中间格式,再通过解释器对其解释执行。

下图是一个编程语言实现中,从输入到输出所涉及的几个的主要环节。


image.png

根据应用场景和使用技术的不同,编程语言可以分为解析器解释器翻译器生成器等多种不同的应用。

例如,对于静态编译型的语言来说,我们需要构造编译器来完成从语法解析、语义分析,到生成机器代码等多个环节,最终实现在某一硬件平台上执行的目标。

同时,对于某些特定领域,我们只需要打造一种专门的语法,通过解析其语法生成一种中间表示(IR, Intermediate representation),而这种中间表示的具体使用则由该领域的应用程序内部来决定。这种类型的应用,往往只会涉及到上图中所示的前面几个阶段,类似于通常编译器设计中的前端(Front End)。我们通常使用XML、JSON作为配置文件,使用正则表达式进行模式匹配;在这些应用场景中,应用程序仅需要解析用这些“语言”编写的“程序”,生成内部使用的中间表示。

解析器(Parser)

编程语言的解析(parsing),就是通过计算机程序来扫描和识别一系列语句的过程。Parser对字符序列进行扫描,按照语言所定义的规则,生成某一种中间形式的数据结构。

识别短语结构

如同人类处理自然语言时识别出语句中的动词和名词一样,计算机在处理程序时,也会识别出一系列扮演不同角色的语法符号(token),如变量、关键字、操作符等。符号的序列组成表达式(expression),表达式的序列组成语句(statement)。

编译器将文本序列解析为解析树(parse tree)的形式。例如,对于这一条语句:

if  x < 0 then x = 0;

其解析之后的解析树的结果为下图所示:


parse tree

从上面生成解析树的过程可以看出,解析(parsing)的过程就是由扁平的符号序列构建二维解析树的过程。

构建递归下降解析器

在对字符序列解析过程中使用一个游标来指示字符的当前位置,在初始时它指向第一个符号。
解析过程中,根据当前游标所指的token类型执行不同的操作。例如,如果是return,则表示这是一个return语句;如果是一个标识符,则表示这是一个赋值语句。这个用于判断语句类型的token,被称为lookahead token
用代码描述就是这样的:

void stat() {
    if ( «lookahead token is return» ) returnstat();
    else if ( «lookahead token is identifier» ) assign(); 
    else if ( «lookahead token is if» ) ifstat(); 
    else «parse error»;
}

如果从parse tree的角度来看,上述解析的过程就是从根节点从上向下,不断递归,识别根节点的过程。因此,这个解析的过程也被称为递归下降解析器

LL(1)、LL(2)与LL(k)

上面构建的递归下降解析器,也被称为LL解析器。第一个L是指读取输入的字符序列从左往右,第二个L指递归访问解析树时,对孩子节点的访问顺序是从左往右。根据使用 lookahead token 的多少,可以分为LL(1)LL(2)以及LL(k),括号中的数字就是向前看的、用于判断语句类型的符号的个数。
在上面的例子中,根据一个符号就可以判断出returnidentiferif等不同的类型,所以是一个 LL(1) parser

Case Study: JSON解析器

JSONJavaScript Object Notation,JavaScript对象表示法)是一种轻量级的数据交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。虽然脱胎于 JavaScript,但JSON 数据格式与编程语言无关,很多编程语言都支持 JSON 格式数据的生成和解析。

在JSON中,一共有以下这么几种基本数据类型,以及它们的任意组合:

  • 字符串:以""括起来的一串字符。
  • 数值:一系列0-9的数字组合,可以为负数或者小数。还可以用e或者E表示为指数形式。
  • 布尔值:表示为true 或者false
  • 值的有序列表(array):一个或者多个值用,分割后,使用[,]括起来就形成了这样的列表,形如:[value, value]
  • 对象(object):一个对象包含一系列非排序的 名称/值对 (pair),一个对象以{开始,并以}结束。每个 名称/值 对之间使用:分割。
  • 数组 (array):一个数组是一个值(value)的集合,一个数组以[开始,并以]结束。数组成员之间使用,分割。
  • 名称/值(pair):名称和值之间使用 : 隔开,一般的形式是:{name:value}。一个名称是一个字符串, 一个值(value)可以是一个字符串(string),一个数值(number),一个对象(object),一个布尔值(bool),一个有序列表(array),或者一个null值。
Jansson解析示例

Jansson是一个用于编码、解析和操作JSON数据的C语言库。下面这段代码是其中JSON字符串解析的核心逻辑。
其中的第一个参数lex是一个词法分析器,完成对JSON字符串的扫描,识别出其中的符号(token)序列。


static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error)
{
    json_t *json;
    
    //...
    switch(lex->token) {
        case TOKEN_STRING: {
            const char *value = lex->value.string.val;
            size_t len = lex->value.string.len;

            if(!(flags & JSON_ALLOW_NUL)) {
                if(memchr(value, '\0', len)) {
                    error_set(error, lex, json_error_null_character, "\\u0000 is not allowed without JSON_ALLOW_NUL");
                    return NULL;
                }
            }

            json = jsonp_stringn_nocheck_own(value, len);
            lex->value.string.val = NULL;
            lex->value.string.len = 0;
            break;
        }

        case TOKEN_INTEGER: {
            json = json_integer(lex->value.integer);
            break;
        }

        case TOKEN_REAL: {
            json = json_real(lex->value.real);
            break;
        }

        case TOKEN_TRUE:
            json = json_true();
            break;

        case TOKEN_FALSE:
            json = json_false();
            break;

        case TOKEN_NULL:
            json = json_null();
            break;

        case '{':
            json = parse_object(lex, flags, error);
            break;

        case '[':
            json = parse_array(lex, flags, error);
            break;

        case TOKEN_INVALID:
            error_set(error, lex, json_error_invalid_syntax, "invalid token");
            return NULL;

        default:
            error_set(error, lex, json_error_invalid_syntax, "unexpected token");
            return NULL;
    }
    //....
    return json;
}

以上解析的过程,就是根据当前token的类型,识别出相应的“语句”
例如:根据符号的类型为TOKEN_STRINGTOKEN_INTEGERTOKEN_REALTOKEN_TRUETOKEN_FALSETOKEN_NULL等值,分别识别出字符串、实数、布尔值、NULL等语句;如果当前符号是{,则表示一个object的开始;符号}则表示一个object的结束。

以上根据当前所指的1token来识别和解析“语言”的过程,可以看做是一个LL(1)解析器。

小结

本文介绍了编译器设计中的基本原理,尤其是跟解析器相关的技术和方法,并以Jansson库中JSON字符串的解析为例进行了说明。
编译器设计号称是程序设计领域中的皇冠。学习和了解其中的方法和技术,也可以帮助我们更好地进行应用程序的设计和开发。

扩展阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 链接地址:https://www.tutorialspoint.com/compiler_design/compi...
    dannyvi阅读 4,694评论 1 12
  • 15天打卡活动,读好好赚钱,悟极简理财 奖励: 挑选的是<<普通人理财学堂>>训练营一期 心得: 基本都是每天早班...
    一只加菲喵阅读 408评论 0 8
  • 突然發現,作家是好多人的夢想 可也不是誰都能寫出好文章 把自己心裡的故事明明白白的說出來,要別人也能明了 要有故事...
    晶悅龍阅读 214评论 0 0
  • 你的儿女,其实不是你的儿女。 他们是生命对于自身渴望而诞生的孩子。 他们借助你来到这世界,却非因你而来, 他们在你...
    喵二姐阅读 209评论 0 0