一个简单的解释器(8)—— symbol table

OK,接下来我们需要面对更加严肃的问题:如何构建一个真正的解释器?
前几章的内容已经足够的有趣,但是仅仅是添加grammar,然后一步一步实现Lexer,AST nodes,Syntax analysis等内容是不够的,我们还要管理代码中的全局变量、局部变量、函数、函数嵌套等等问题,为此,我们需要打下一个足够好的地基,然后才能真正地实现一个解释器,并且不至于让代码变成屎山。

Symbol table

首先,我们引入一个新的概念:符号表(Symbol table),符号表需要完成这样一个功能:存储代码中的所有符号和它对应的类型。
比如:

PROGRAM part8;
VAR:
    a: INTEGER;
BEGIN
END;

这段代码里,有两个符号:a和INTEGER,INTEGER是内置类型,a是INTEGER类型的变量。
为此,我们先写一个Symbol类:

#pragma once
#include <string>
#include <memory>

class Symbol;
typedef std::shared_ptr<Symbol> SymbolPtr;
typedef Symbol* const SymbolRawPtr;

class Symbol
{
protected:
    std::string symbolName;
    SymbolPtr symbolType;
public:
    virtual SymbolRawPtr getType() = 0;
    std::string getName();
};

它负责存储符号的名字跟类型,并且类型本身又指向一个Symbol类,然后写一个继承了Symbol类的BuiltinTypeSymbol类来表示内置类型:

#pragma once
#include "Symbol/Symbol.h"

class BuiltinTypeSymbol : public Symbol
{
public:
    BuiltinTypeSymbol(std::string sName);
    SymbolRawPtr getType();
};

它不需要做任何事,只需要在getType时返回自身即可:

#include "Symbol/BuiltinTypeSymbol.h"

BuiltinTypeSymbol::BuiltinTypeSymbol(std::string sName)
{
    symbolName = sName;
}

SymbolRawPtr BuiltinTypeSymbol::getType()
{
    return this;
}

对于变量,定义一个VarSymbol:

#pragma once
#include "Symbol/Symbol.h"

class VarSymbol : public Symbol
{
public:
    VarSymbol(std::string varName, SymbolPtr varType);
    SymbolRawPtr getType();
};

它需要记录两个成员:变量名和变量类型,并且getType时返回varType所指向的Symbol,这里,varType会指向一个BultinTypeSymbol。
接下来,我们需要一张符号表,它负责记录代码中所有的符号:

#pragma once
#include <map>
#include <string>
#include "Symbol/Symbol.h"

class SymbolTable
{
private:
    std::map<std::string, SymbolPtr> symbols;
public:
    SymbolTable();
    void define(SymbolPtr symbolPtr);
    SymbolPtr lookup(std::string symbolName);
    std::map<std::string, SymbolPtr> getAll() const;
};

这里主要需要实现两个函数:define和lookup,以便通过符号名找到对应的符号:

#include "Symbol/SymbolTable.h"
#include "Symbol/BuiltinTypeSymbol.h"

SymbolTable::SymbolTable()
{
    define(std::make_shared<BuiltinTypeSymbol>("INTEGER"));
    define(std::make_shared<BuiltinTypeSymbol>("REAL"));
}

void SymbolTable::define(SymbolPtr symbolPtr)
{
    symbols[symbolPtr->getName()] = symbolPtr;
}

SymbolPtr SymbolTable::lookup(std::string symbolName)
{
    if (symbols.find(symbolName) == symbols.end()) 
    {
        throw "symbol error";
    }
    return symbols[symbolName];
}

std::map<std::string, SymbolPtr> SymbolTable::getAll() const
{
    return symbols;
}

注意这里将INTEGER和REAL两个符号作为内置类型,在构造时调用define函数。
下一步,实现一个SymbolTableBuilder,它继承自NodeVisitor,但是不同于Interpreter,它不用解释代码,它要做的只是访问node时记录代码中的符号,声明如下:

#pragma once
#include "NodeVisitor/NodeVisitor.h"
#include "ASTNodes/ProgramNode.h"
#include "ASTNodes/BlockNode.h"
#include "ASTNodes/BinOpNode.h"
#include "ASTNodes/NumNode.h"
#include "ASTNodes/UnaryOpNode.h"
#include "ASTNodes/CompoundNode.h"
#include "ASTNodes/NoOpNode.h"
#include "ASTNodes/VarDeclNode.h"
#include "ASTNodes/VarNode.h"
#include "ASTNodes/AssignNode.h"
#include "Symbol/SymbolTable.h"

class SymbolTableBuilder : public NodeVisitor
{
private:
    SymbolTable symTable;
private:
    DECLARE_VISITOR(ProgramNode);
    DECLARE_VISITOR(BlockNode);
    DECLARE_VISITOR(BinOpNode);
    DECLARE_VISITOR(NumNode);
    DECLARE_VISITOR(UnaryOpNode);
    DECLARE_VISITOR(CompoundNode);
    DECLARE_VISITOR(NoOpNode);
    DECLARE_VISITOR(VarDeclNode);
    DECLARE_VISITOR(VarNode);
    DECLARE_VISITOR(AssignNode);
public:
    SymbolTableBuilder();
    SymbolTable getSymbolTable() const;
};

类似Interpreter,SymbolTableBuilder需要对每一种node实现一个visit函数,其中最关键的是VarDeclNode和AssignNode,他们分别实现变量的声明和赋值(这里暂时不实现赋值的逻辑,只是调用lookup):

SYM_TABLE_BUILDER_VISITOR(VarDeclNode) 
{
    auto node = std::dynamic_pointer_cast<VarDeclNode>(nodePtr);
    auto typeName = std::get<std::string>(node->getType()->getTokenPtr()->getValue());
    auto typeSymbol = symTable.lookup(typeName);
    std::string varName = std::get<std::string>(node->getVar()->getTokenPtr()->getValue());
    symTable.define(std::make_shared<VarSymbol>(varName,typeSymbol));
    return NULL;
}

SYM_TABLE_BUILDER_VISITOR(AssignNode)
{
    auto node = std::dynamic_pointer_cast<AssignNode>(nodePtr);
    auto varNode = std::dynamic_pointer_cast<VarNode>(node->getLeftPtr());
    std::string varName = std::get<std::string>(varNode->getTokenPtr()->getValue());
    symTable.lookup(varName);
    this->visit(node->getRightPtr());
    return NULL;
}

最后,写一段代码验证一下:

int main()
{
    std::string text = R"(
PROGRAM Part8;
VAR
   number : INTEGER;
   a, b   : INTEGER;
   y      : REAL;

BEGIN {Part8}
   number := 2;
   a := number ;
   b := 10 * a + 10 * number DIV 4;
   y := 20 / 7 + 3.14
END.  {Part8}
)";
    ASTParser parser(text);
    SymbolTableBuilder symtableBuilder;
    symtableBuilder.visit(parser->parse());
    for (const auto pair : symtableBuilder.getSymbolTable().getAll())
    {
        cout << pair.first << " : " << pair.second->getType()->getName() << endl;
    }
    cout << endl;
    return 0;
}

顺利的话,可以看到输出结果如下:

INTEGER : INTEGER
REAL : REAL
a : INTEGER
b : INTEGER
number : INTEGER
y : REAL

当然这里还有一个问题:没有实现变量作用域,这个问题我们将来再说。

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

推荐阅读更多精彩内容