LLVM12第三章之词法分析

1、Cale语言介绍

1.1、cale语言的语法形式(非二义性且非左递归文法)

在这里插入图片描述

1.2、cale语言示例

在这里插入图片描述

2、词法分析器

2.1、StringRef类

下图为llvm::StringRef类的源码截图。可以看到创建StringRef对象不会创建一个新的字符串,而是记录一个指向字符串的指针和字符串的长度。
在这里插入图片描述

2.2、Token类

Token类用于将每一个识别的token进行对象封装。Token类的属性为Kind与Text,分别记录当前Token对象的类型及字符串文本。

class Token {
  // 友元类,表示词法分析类Lexer可访问Token类中属性和方法
  friend class Lexer;

public:
	//	内部枚举类(Toke类型)
  enum TokenKind : unsigned short {
    eoi,	 // 文本终止符,表示已经读取文本末尾,后续无字符
    unknown, // 未知类型Token
    ident,   // 标识符
    number,	 // 数字
    comma,   // 逗号
    colon,   // 冒号
    plus,    // 加号
    minus,   // 减号
    star,    // 乘号
    slash,   // 除号
    l_paren, // 左括号
    r_paren, // 右括号
    KW_with  // with关键字
  };
private:
  // Token类型
  TokenKind Kind;
  // Token对应的文本字符串
  llvm::StringRef Text;

public:
  // 获取Token的类型
  TokenKind getKind() const { return Kind; }
  // 获取Token的文本字符串
  llvm::StringRef getText() const {
    return Text;
  }
  // 判断当前Token对象是否是类型K
  bool is(TokenKind K) const { return Kind == K; }
  // 判断当前Token对象是否是类型K1或类型K2
  bool isOneOf(TokenKind K1, TokenKind K2) const {
    return is(K1) || is(K2);
  }
  // 可变参数模块,通过递归的形式展开参数包,判断Token类型是否为参数列表中的类型之一
  template <typename... Ts>
  bool isOneOf(TokenKind K1, TokenKind K2, Ts... Ks) const {
    return is(K1) || isOneOf(K2, Ks...);
  }
};

2.3、Lexer类定义

Lexer类为词法分析器,通过该类提供的next方法,使用者能一个一个的按序读取Token对象。
该类主要有两个方法:

  1. void next(Token &token)读取一个连续无空格字符串,封装为Token对象到token对象中。
  2. void formToken(Token &Result, const char *TokEnd, Token::TokenKind Kind)设置Token对象Result的类型和文本,并修改BufferPtr的指向,确保BufferPtr指向下一个将要识别的字符。

class Lexer {
  // 始终指向输入表达式字符串的开始位置
  const char *BufferStart;
  // 指向当前识别的字符
  const char *BufferPtr;

public:
  Lexer(const llvm::StringRef &Buffer) {
    BufferStart = Buffer.begin();
    BufferPtr = BufferStart;
  }
  // 获取下一个Token对象,保存到token中
  void next(Token &token);

private:
  // 设置Token对象Result的类型和文本字符串,并将指针BufferPtr移到下一个要识别的字符
  void formToken(Token &Result, const char *TokEnd,
                 Token::TokenKind Kind);
};

2.4、词法分析的工具函数

在命名空间charinfo中创建三个函数,分别用于判断当前识别的字符是否为空格、数字、文本字符。

namespace charinfo {
LLVM_READNONE inline bool isWhitespace(char c) {
  return c == ' ' || c == '\t' || c == '\f' || c == '\v' ||
         c == '\r' || c == '\n';
}

LLVM_READNONE inline bool isDigit(char c) {
  return c >= '0' && c <= '9';
}

LLVM_READNONE inline bool isLetter(char c) {
  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
}

2.5、Lexer类的成员函数实现

2.5.1、next函数
// 读取下一个Token对象,保存到token中
void Lexer::next(Token &token) {
  // 读取到空格,跳过
  while (*BufferPtr && charinfo::isWhitespace(*BufferPtr)) {
    ++BufferPtr;
  }
  // 读取完表达式字符串,返回类型为eoi(文本终止符)的token
  if (!*BufferPtr) {
    token.Kind = Token::eoi;
    return;
  }
  // 如果读取到字符文本,认为当前token类型为with关键字或标识符,循环读取完整个字符串文本
  if (charinfo::isLetter(*BufferPtr)) {
    // end指针最终指向识别的文本字符串的下一个字符
    const char *end = BufferPtr + 1;
    while (charinfo::isLetter(*end))
      ++end;
    llvm::StringRef Name(BufferPtr, end - BufferPtr);
    Token::TokenKind kind =
        Name == "with" ? Token::KW_with : Token::ident;
    // 返回类型为KW_with或ident的token对象,并把BufferPtr更新为end指向的位置
    formToken(token, end, kind);
    return;
  } 
  // 如果识别到的是数字,循环读取完整个数字串成封装为类型为number的Token对象
  else if (charinfo::isDigit(*BufferPtr)) {
    const char *end = BufferPtr + 1;
    while (charinfo::isDigit(*end))
      ++end;
    formToken(token, end, Token::number);
    return;
  }
  // 对于非字母、非数字、非空格,认为当前token是运算符号之一,通过case语句封装为对应的token
  else {
    switch (*BufferPtr) {
#define CASE(ch, tok) \
case ch: formToken(token, BufferPtr + 1, tok); break
CASE('+', Token::plus);
CASE('-', Token::minus);
CASE('*', Token::star);
CASE('/', Token::slash);
CASE('(', Token::Token::l_paren);
CASE(')', Token::Token::r_paren);
CASE(':', Token::Token::colon);
CASE(',', Token::Token::comma);
#undef CASE
    default:
      formToken(token, BufferPtr + 1, Token::unknown);
    }
    return;
  }
}
2.5.2、formToken函数
// Tok为要封装的Token对象,TokEnd为当前Token字符串的下一个字符,用于更新BufferPtr,确保BufferPtr在识别完一个toekn后指向下一个要识别的位置
void Lexer::formToken(Token &Tok, const char *TokEnd,
                      Token::TokenKind Kind) {
  Tok.Kind = Kind;
  Tok.Text = llvm::StringRef(BufferPtr, TokEnd - BufferPtr);
  BufferPtr = TokEnd;
}

3、代码测试

#include"./Lexer.h"
#include <iostream>
using namespace std;
int main()
{
    Lexer lex("with a: a*3");
    Token token;
    while(true)
    {
        lex.next(token);
        if(token.getKind() == Token::TokenKind::eoi)
        {
            std::cout << "token识别完毕" << std::endl;
            break;
        }
        std::cout <<"类型: " << token.getKind() << " token文本:" << token.getText().str() << endl;
    }
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值