编译器架构的王者LLVM——(9)栈式符号表的构建

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 —— 题记

版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy

栈式符号表的构建

栈式符号表对于一款编译器,无疑是核心的组件。

无论你在做什么符号扫描,那么都离不开符号表,如何得知一个符号是否定义,以及它的类型,那么唯有查看符号表中的记录。

栈式符号表并不复杂,但思想精妙,本文,将介绍一款栈式符号表的原理及简单构建。

源代码的例子

首先我们来看一段C代码

int a[3] = { 100, 10, 1};

int work() {
    if (a[0] == 100) { // 这里的a指向的是全局符号a
        int a = 99999; // 重新定义了局部符号    下图的符号表是扫描到这里后的情况
        for (int i = 0; i< 10; ++i) {
            a /= 3; // 由于局部符号优先级较高,引用局部符号
        }
        return a; // 局部符号
    }
    return a[0]; // 局部符号生命周期已过,找到全局符号
}

于是我们发现,符号表在局部声明变量后,将局部符号增加了,这和全局符号并不冲突,而是优先级不同,越靠近栈顶,越先发现

用C++的map和stack构建符号表

如果考虑效率的话,最佳选择是用C语言构建符号表,这样操作起来会更快,但我们毕竟目前考虑开发的简便型而言,用C++的map就可以方便地实现符号表。

首先我们做一个局部符号表,由于其中不会有重复的符号名,所以我们只要简单的将其存放起来即可。

然后符号表还需要记录很多类型信息、指针信息等,我们设计一个结构体表示它们:

enum SymbolType
{
    var_t, type_t, struct_t, enum_t, delegate_t, function_t
};

struct id {
    int        level;
    SymbolType type;
    void*      data;
};

我们目前是简单起见,由于还不知道都可能放哪些东西,例如数组符号,肯定要包含数组长度、维度等信息,各种变量都会包含类型信息,所以我们这里放了一个void*的指针,到时候需要的化,就强制转换一下。

这里其实定义一个基类,需要存储的内容去多态派生也是可以的,没做主要是因为可能存放的东西类型很多,暂时先用一个void*,这样可能方便一点。

于是我们的局部符号表就有了:

class IDMap
{
public:
    IDMap();
    ~IDMap();
    id* find(string& str) const; // 查找一个符号
    void insert(string& str, int level, SymbolType type, void* data); // 插入一个符号
private:
    map<string,id*> ID_map;
};

我想查找和插入都是C++中map的基础函数,大家应该很轻松就能实现吧。

再弄一个栈来存储一个IDMap:

class IDTable
{
public:
    IDTable();
    id* find(string& str) const;
    void insert(string& str,SymbolType type, void* data);
    void push(); // 压栈和弹栈操作,例如在函数的解析时,需要压栈,一个函数解析完,就弹栈
    void pop();
    int getLevel(); // 获取当前的层级,如果为0,则说明是只有全局符号了
private:
    deque<IDMap> ID_stack;
};

这里用deque而没用stack的原因是,deque支持随机访问,而stack只能访问栈顶。

寻找时,就按照从栈顶到栈底的顺序依次寻找符号:

id* IDTable::find(string& idname) const {
    for (auto p = ID_stack.rbegin(); p != ID_stack.rend(); ++p) {
        const IDMap& imap = *p;
        id* pid = imap.find(idname);
        if (pid != NULL) return pid;
    }
    return NULL;
}

插入时,就往栈顶,当前最新的符号表里面插入:

void IDTable::insert(string& str,SymbolType type, void* data) {
    IDMap& imap = ID_stack.back();
    imap.insert(str,getLevel(), type, data);
}

这样,一款简易的栈式符号表就构建好了。

附1:Github参考源码

idmap.h

idmap.cpp

idtable.h

idtable.cpp

附2:Graphviz的绘图源码

Graphviz绘图真的非常爽,上面的数据结构图就是用它的dot画的,想了解的朋友可以参考我之前写的 结构化图形绘制利器Graphviz

digraph g {
    graph [
        rankdir = "LR"
    ];
    node [
        fontsize = "16"
        shape = "ellipse"
    ];
    edge [
    ];

    "node0" [
        label = "<f0> stack | <f1> | <f2> | ..."
        shape = "record"
    ];

    "node1" [
        label = "<f0> 全局符号 | a | work |  | ..."
        shape = "record"
    ]

    "node2" [
        label = "<f0> 局部符号 | a |  | ..."
        shape = "record"
    ]

    "node0":f1 -> "node1":f0 [
        id = 0
    ];

    "node0":f2 -> "node2":f0 [
        id = 1
    ];

}

版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy

时间: 2024-10-13 22:47:25

编译器架构的王者LLVM——(9)栈式符号表的构建的相关文章

编译器架构的王者LLVM——(7)函数的翻译方法

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 函数的翻译方法 前面介绍了许多编译器架构上面的特点,如何组织语法树.如果多遍扫描语法树.今天开始,我们就要设计本编译器中最核心的部分了,如何设计一个编译时宏,再利用LLVM按顺序生成模块. 设计宏 我们

编译器架构的王者LLVM——(10)变量的存储与读取

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 变量的存储与读取 变量是一款编程语言中的核心,说编译语言是一种符号处理工具,其实是有些道理的.栈式符号表可以方便的记录编译过程中的变量和语法符号,我们上节已经了解了其中的实现方法.那么,还有没有其他的办

编译器架构的王者LLVM——(11)深入理解GetElementPtr

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 深入理解GetElementPtr LLVM平台,和C语言极为类似,强类型,需要复杂的指针操作,基于系统的符号调用等.而LLVM的指针操作指令,GetElementPtr,几乎是所有指针计算的关键,而理

编译器架构的王者LLVM——(8)函数的调用及基本运算符

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 函数的调用及基本运算符 之前我们提到了函数的定义,那么,定义好的函数如何调用才行呢?今天我们就来了解一下,函数的调用. 函数调用的宏形式 我们去读之前对函数调用的语法树翻译形式: printf("%d\

编译器架构的王者LLVM——(5)语法树模型的基本结构

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 语法树模型的基本结构 上次我们看了Lex和Yacc的翻译文件,可能一些朋友并不了解其中的执行部分,而且,对这个抽象语法树是怎么构建起来的还不清楚.今天我们就再详细介绍一下如果方便的构建一棵抽象语法树(A

扩充C0文法编译器开发笔记(一)符号表

零.简介 这是一个编译大作业. 一.扩充C0文法 文法包括 常量说明和定义.变量说明和定义.无返回值函数定义和调用.有返回值函数定义和调用.条件语句.while循环语句.情况语句.赋值语句.返回语句.读语句.写语句,支持加减乘除四则运算.整数比较运算.包含一维数组.不包含实型.不包含for循环语句.程序由main方法进入.具体文法见下: 1 <加法运算符> ::= +|- 2 <乘法运算符> ::= *|/ 3 <关系运算符> ::= <|<=|>|&

数据层全栈式编程架构

CRUD全栈式编程架构之数据层的设计 CodeFirst 一直以来我们写应用的时候首先都是创建数据库 终于在orm支持codefirst之后,我们可以先建模. 通过模型去创建数据库,并且基于codefirst可以实现方便的 实现数据库迁移的工作.使用codefirst有以下几个技巧, 以EntityFramework为例,结合我这个设计做了以下改进 1.模型的识别 建立一个基类命名Entity,里面只有一个long类型的id字段. 所有需要映射到数据库的模型都继承自Entity, + 2.模型的

编译器实践一 之 加法栈式计算机

下面是一个简单的小型加法栈式计算机 #include <stdio.h> #include <stdlib.h> /////////////////////////////////////////////// // Data structures for the Sum language. enum Exp_Kind_t {EXP_INT, EXP_SUM}; struct Exp_t { enum Exp_Kind_t kind; }; struct Exp_Int { enum

CRUD全栈式编程概述

业务场景 CRUD,从数据驱动的角度几乎所有的的业务都是在做这样的事情.  几乎所有的操作都是在做对表的增删该查.  假设我们将数据库数据规个类:  分为基础/配置数据和业务/增长数据,或者说静态数据和动态数据.  其中静态数据是由后台管理员编辑的产生,动态数据是由客户产生.  那么这部分中的静态数据往往伴随着完整的增删改查逻辑.  完整的增删改查逻辑指的是,有对数据库某个表数据的查询.  一条或者几条数据的添加,删除,修改.  再直白一点就是有个界面,上面有查询,添加,删除,修改,导入,导出的