在编程语言解析器中,注释功能是提高代码可读性的重要组成部分。本章节将介绍如何在词法解析器中实现注释功能,包括单行注释和多行注释的处理。
注释语法设计
我们定义两种常见的注释语法:
java
// 单行注释 - 从//开始到行尾的内容都被视为注释
/*
多行注释 - 从/*开始到*/结束的内容都被视为注释
可以跨越多行
*/
实现思路
实现注释功能主要涉及词法解析器的修改,核心是添加两个关键函数来跳过注释内容:
skipComments()
- 用于跳过单行注释skipMulComments()
- 用于跳过多行注释
修改词法解析器
下面是改进后的词法解析器代码,添加了完整的注释处理功能:
java
// 词法解析器 - 负责将源代码转换为词法单元(tokens)
public class Lexer {
private String text; // 输入的程序文本
private Integer position; // 记录当前扫描位置的指针
private Character currentChar; // 当前正在扫描的字符
private Map<String, Token> keyWordMap = new HashMap<>(); // 存储关键字的映射表
/**
* 不改变当前位置的情况下获取下一个词法单元
* 用于预览下一个token而不实际消费输入
*/
public Token peekToken() {
Integer lastPosition = position;
Character lastChar = currentChar;
Token token = getNextToken();
position = lastPosition;
currentChar = lastChar;
return token;
}
/**
* 核心方法:获取下一个词法单元
* 该方法根据当前字符判断应该生成哪种类型的token
*/
public Token getNextToken() {
while (this.currentChar != null) {
if (Character.isDigit(this.currentChar)) {
// 处理数字
return this.integer();
} else if (Character.isWhitespace(currentChar)) {
// 跳过空白字符
this.skipWhiteSpace();
} else if (this.currentChar == '+') {
// 处理加法运算符
Token token = new Token(TokenType.PLUS, "+";
this.advance();
return token;
} else if (this.currentChar == '-') {
// 处理减法运算符
Token token = new Token(TokenType.MINUS, "-");
this.advance();
return token;
} else if (this.currentChar == '*') {
// 处理乘法运算符
Token token = new Token(TokenType.MUL, "*");
this.advance();
return token;
} else if (currentChar == '/' && peek(1) == '/') {
// 检测到单行注释 //
this.skipComments();
} else if (currentChar == '/' && peek(1) == '*') {
// 检测到多行注释 /*
this.skipMulComments();
} else if (this.currentChar == '/') {
// 处理除法运算符
Token token = new Token(TokenType.DIV, "/");
this.advance();
return token;
} else if (this.currentChar == '(') {
// 处理左括号
Token token = new Token(TokenType.LBRACKET, "(");
this.advance();
return token;
} else if (this.currentChar == ')') {
// 处理右括号
Token token = new Token(TokenType.RBRACKET, ")");
this.advance();
return token;
} else if (this.currentChar == '{') {
// 处理左花括号
Token token = new Token(TokenType.LBRACE, "{");
this.advance();
return token;
} else if (this.currentChar == '}') {
// 处理右花括号
Token token = new Token(TokenType.RBRACE, "}");
this.advance();
return token;
} else if (this.currentChar == '=') {
// 处理赋值运算符
Token token = new Token(TokenType.ASSIGN, "=");
this.advance();
return token;
} else if (this.currentChar == ',') {
// 处理逗号
Token token = new Token(TokenType.COLON, ",");
this.advance();
return token;
} else if (this.currentChar == '\'') {
// 处理字符串
return this.string();
} else if (Character.isAlphabetic(currentChar)) {
// 处理变量或关键字
return variable();
} else {
// 遇到未知字符,抛出错误
this.error("未知的词法单元: " + currentChar);
}
}
// 到达输入末尾,返回EOF token
return new Token(TokenType.EOF);
}
/**
* 处理字符串字面量
* 假设字符串由单引号包围
*/
private Token string() {
String value = "";
this.advance(); // 跳过开始的单引号
while (currentChar != null && currentChar != '\'') {
value += currentChar;
this.advance();
}
this.advance(); // 跳过结束的单引号
return new Token(TokenType.STRING, value);
}
/**
* 预览当前位置之后的字符,不实际移动位置指针
* @param num 要预览的字符相对于当前位置的偏移量
* @return 预览位置的字符,如果超出范围则返回null
*/
private Character peek(Integer num) {
Integer pos = this.position + num;
if (pos > this.text.length() - 1) {
return null;
} else {
return text.charAt(pos);
}
}
/**
* 跳过多行注释
* 从/*开始到*/结束
*/
private void skipMulComments() {
// 首先跳过/*符号
this.advance();
this.advance();
// 循环读取直到遇到*/或文件结束
while (currentChar != null && !(currentChar == '*' && peek(1) == '/')) {
this.advance();
}
// 如果找到了结束标记,跳过*/
if (currentChar != null) {
this.advance();
this.advance();
}
}
/**
* 跳过单行注释
* 从//开始到行尾
*/
private void skipComments() {
// 跳过//符号
this.advance();
this.advance();
// 循环读取直到遇到换行符或文件结束
while (currentChar != null && currentChar != '\r' && currentChar != '\n') {
this.advance();
}
}
/**
* 处理变量名或关键字
*/
private Token variable() {
String value = "";
// 读取所有字母字符
while (currentChar != null && Character.isAlphabetic(currentChar)) {
value += currentChar;
this.advance();
}
// 检查是否是关键字
Token token = keyWordMap.getOrDefault(value, new Token(TokenType.ID, value));
return token;
}
/**
* 处理整数
*/
public Token integer() {
String result = "";
// 读取所有数字字符
while (this.currentChar != null && Character.isDigit(this.currentChar)) {
result += this.currentChar;
this.advance();
}
return new Token(TokenType.INTEGER, Integer.valueOf(result));
}
/**
* 跳过空白字符(空格、制表符等)
*/
private void skipWhiteSpace() {
while (currentChar != null && Character.isWhitespace(currentChar)) {
this.advance();
}
}
/**
* 将位置指针向前移动一位
*/
public void advance() {
this.position += 1;
if (this.position <= this.text.length() - 1) {
// 位置有效,更新当前字符
this.currentChar = text.charAt(this.position);
} else {
// 已经到达文件末尾
this.currentChar = null;
}
}
/**
* 抛出错误信息
*/
public void error(String msg) {
throw new RuntimeException(msg);
}
/**
* 构造函数,初始化词法解析器
*/
public Lexer(String text) {
this.text = text;
this.position = 0;
this.currentChar = text.charAt(this.position);
// 初始化关键字映射
keyWordMap.put("print", new Token(TokenType.PRINT, "print"));
keyWordMap.put("return", new Token(TokenType.RETURN, "return"));
keyWordMap.put("function", new Token(TokenType.FUNCTION, "function"));
}
}
代码优化说明
在优化后的代码中,我做了以下改进:
- 修复了注释检测逻辑 :将原来错误的多行注释检测
"/**"
改为标准的"/*"
- 删除了重复代码 :移除了
getNextToken()
方法中重复的除法运算符处理代码 - 改进了注释处理流程 :在
skipComments()
和skipMulComments()
方法中,先跳过注释开始标记,再进行内容处理 - 增加了更详细的注释:为每个方法和关键逻辑添加了清晰的文档注释
- 增强了错误处理:在错误信息中包含了导致问题的具体字符
使用示例
下面是一个简单的示例,展示如何使用这个支持注释的词法解析器:
java
public class LexerDemo {
public static void main(String[] args) {
// 包含各种注释的示例代码
String code = "// 这是一个示例程序\n" +
"function add(a, b) {\n" +
" /*\n" +
" * 这是一个加法函数\n" +
" * 接收两个参数并返回它们的和\n" +
" */\n" +
" return a + b; // 返回计算结果\n" +
"}";
// 创建词法解析器
Lexer lexer = new Lexer(code);
// 逐个获取并打印词法单元,直到EOF
Token token;
do {
token = lexer.getNextToken();
System.out.println(token);
} while (token.getType() != TokenType.EOF);
}
}
运行上述代码,词法解析器将正确处理代码中的注释内容,只输出实际的代码词法单元,而忽略所有注释部分。
总结
通过本章节的学习,我们了解了如何在词法解析器中实现注释功能。主要步骤包括:
- 定义注释的语法规则(单行注释和多行注释)
- 在词法解析器中添加专门的函数来识别和跳过注释内容
- 在主扫描逻辑中优先检测注释模式
实现注释功能不仅提高了代码的可读性,也使解析器更加健壮,能够处理实际开发中的各种代码格式。