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对象。
该类主要有两个方法:
void next(Token &token)
读取一个连续无空格字符串,封装为Token对象到token对象中。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;
}
}