基于llvm自制编译器(1):Kaleidoscope、词法分析器

文章介绍了Kaleidoscope编程语言,它支持基本的函数定义和数学运算。词法分析器(Lexer)是编译器的一部分,负责将输入分解为tokens,如标识符、数字和关键字。当遇到tok_identifier或tok_number时,会存储相应的标识符名或数值。gettok函数是词法分析器的核心,处理标识符、数字、注释和EOF。接下来的内容将涉及语法分析器和驱动器的实现。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、Kaleidoscope

提示:这里可以添加本文要记录的大概内容:

本章节朱啊哟介绍一种sub编程语言——Kaleidoscope。该语言支持函数定义、条件语句、数学运算等。后续我们对该语言的语言特征进行扩展,支持if/then/else语句、for循环、自定义操作符、JIT编译、调试信息等。

为了简化数据类型,我们设计的Kaleidoscope只有一种数据类型(64位浮点类型),即C语言中的double类型。因此所有值都是隐式双进度类型,并且无需进行类型申明。如下所示,为基于Kaleidoscope的斐波拉契计算方法。

def fib(x)
	if x< 3 then
	 1
	else
	 fib(x-1) + fib(x-2)
	 
fib(40)

此外,我们将支持Kaleidoscope调用标准库函数。因此,我们可以在使用函数之前使用extern关键字来定义函数。例如:

extern sin(arg);
extern cos(arg);
extern atan2(arg1 arg2);

atan2(sin(.4), cos(42)

实现编程语言的本质其实是实现编译程序语言的编译器
下面将开始为Kaleidoscope语言逐步实现编译器

二、词法分析器

first of all,我们要实现编译器的文本处理和内容识别能力,即词法分析器(Lexer)。词法分析器将输入内容分解为token, 词法分析器返回的每一个token都包含相关元数据,如:token值、token类型等。如下所示,为token的定义类型。

// The lexer returns tokens [0-255] if it is an unknown character, otherwise one
// of these for known things.
enum Token {
    tok_eof = -1,
    
    // commands
    tok_def = -2,
    tok_extern = -3,
    
    // primary
    tok_identifier = -4,
    tok_number = -5,
}

static std::string IdentifierStr; // Filled in if tok_identifier
static double NumVal;             // Filled in if tok_number

此法分析器返回的token类型有两大类:

  • 预定义类型,即Token枚举中定义的枚举值,使用负整数表示。
  • 未知字符,如+,使用[0-255]正整数表示

当词法分析器解析到tok_identifier类型或者tok_number类型的token时,会进行以下处理。

  • 如果当前token类型是tok_identifier时,则使用全局变量IdentifierStr保存标识符的名称,作为token的值。
  • 如果当前token类型是tok_number,则使用全局变量NumVal保存数值,作为token值。

注意,这里其实与LEX工具使用yyval保存token值,yytext保存标识符有着异曲同工之妙。

此法分析器的核心部分由gettok函数实线。每一次调用gettok函数都将从标准输入中返回下一个token。gettok的具体实线如下所示。

#include <stdio.h>
#include <ctype.h>

#include "token.h"

///gettok -Return the next token from standard input.

static int gettok(){
    static int LastChar = ' ';
    
    // Skip any whitespace 
    while(isspace(LastChar))
    LastChar = getchar();

    // identifier : [a-zA-Z][a-zA-Z0-9]*
    if (isalpha(LastChar)){
        
        IdentifierStr = LastChar;
        while (isalnum(LastChar = getChar()))
            IdentifierStr += LastChar;
        
        if(IdentifierStr == "def")
            return tok_def;

        if(IdentifierStr == "extern")
            return tok_extern;
        
        return tok_identifier;
        
    }

    if(isdigit(LastChar) || LastChar = '.'){
        std::string NumStr;

        do{
            NumStr += LastChar;
            LastChar = getchar();

        }while (isdigit(LastChar) || LastChar == '.');
        
        NumVal = strtod(NumStr.c_str(), nullptr);

        return tok_number;
    }

      if (LastChar == '#') {
    // Comment until end of line.
    do
      LastChar = getchar();
    while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');

    if (LastChar != EOF)
      return gettok();
  }

  // Check for end of file.  Don't eat the EOF.
  if (LastChar == EOF)
    return tok_eof;

  // Otherwise, just return the character as its ascii value.
  int ThisChar = LastChar;
  LastChar = getchar();
  return ThisChar;
 }

gettok函数使用C函数的getChar()读取标准输入字符,并通过while循环来忽略token之间的空格。然后,根据第一个字符进行分类处理。

  • 如果第一个字符是字母,那么gettok开始识别标识符和关键字,并将标识符存入IdentifierStr全局变量,返回对应的token类型。
  • 如果第一个字符是数字或.,那么gettok开始识别数值,并将数值存入NumVal全局变量,返回token类型tok_number
  • 如果第一个字符是#,那么gettok识别注释,该符号之后郑航内容均为注释内容,进行忽略处理。
  • 如果第一个字符是EOF,那么识别为文件结束符,返回token类型token_eof
  • 其他情况,返回该字符的ASCII值。
    如下所示,为gettoken函数的工作原理示意图。
    在这里插入图片描述
    至此,我们已经实现了Kaleidoscope的词法分析器。下一章,我们将实现语法分析器(Parser),从而构建抽象语法树(Abstract Syntax Tree)。当实现了词法分析器和语法分析器后,我们会实现一个驱动器(Driver)来使两者协同进行工作,并进行测试。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值