章节16:实现注释功能

在编程语言解析器中,注释功能是提高代码可读性的重要组成部分。本章节将介绍如何在词法解析器中实现注释功能,包括单行注释和多行注释的处理。

注释语法设计

我们定义两种常见的注释语法:

java 复制代码
// 单行注释 - 从//开始到行尾的内容都被视为注释

/* 
   多行注释 - 从/*开始到*/结束的内容都被视为注释
   可以跨越多行
*/

实现思路

实现注释功能主要涉及词法解析器的修改,核心是添加两个关键函数来跳过注释内容:

  1. skipComments() - 用于跳过单行注释
  2. 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"));
    }
}

代码优化说明

在优化后的代码中,我做了以下改进:

  1. 修复了注释检测逻辑 :将原来错误的多行注释检测 "/**" 改为标准的 "/*"
  2. 删除了重复代码 :移除了 getNextToken() 方法中重复的除法运算符处理代码
  3. 改进了注释处理流程 :在 skipComments()skipMulComments() 方法中,先跳过注释开始标记,再进行内容处理
  4. 增加了更详细的注释:为每个方法和关键逻辑添加了清晰的文档注释
  5. 增强了错误处理:在错误信息中包含了导致问题的具体字符

使用示例

下面是一个简单的示例,展示如何使用这个支持注释的词法解析器:

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);
    }
}

运行上述代码,词法解析器将正确处理代码中的注释内容,只输出实际的代码词法单元,而忽略所有注释部分。

总结

通过本章节的学习,我们了解了如何在词法解析器中实现注释功能。主要步骤包括:

  1. 定义注释的语法规则(单行注释和多行注释)
  2. 在词法解析器中添加专门的函数来识别和跳过注释内容
  3. 在主扫描逻辑中优先检测注释模式

实现注释功能不仅提高了代码的可读性,也使解析器更加健壮,能够处理实际开发中的各种代码格式。

相关推荐
whitepure2 分钟前
我如何理解与追求整洁代码
java·后端·代码规范
用户83562907805113 分钟前
Java高效读取Excel表格数据教程
java·后端
yinke小琪16 分钟前
今天解析一下从代码到架构:Java后端开发的"破局"与"新生"
java·后端·架构
码出极致19 分钟前
支付平台资金强一致实践:基于 Seata TCC+DB 模式的余额扣减与渠道支付落地案例
后端·面试
掘金一周24 分钟前
DeepSeek删豆包冲上热搜,大模型世子之争演都不演了 | 掘金一周 8.28
前端·人工智能·后端
静凇24 分钟前
在 Ubuntu 24.04 和 Debian 12.10 中安装 Docker 和 Docker Compose,并使用轩辕镜像加速拉取镜像
后端
tongsound36 分钟前
Cyber RT 调度机制(Scheduler)
架构
FogLetter37 分钟前
Prisma + Next.js 全栈开发初体验:像操作对象一样玩转数据库
前端·后端·next.js
文心快码BaiduComate42 分钟前
新增Zulu-CLI、企业版对话支持自定义模型、一键设置自动执行、复用相同终端,8月新能力速览!
前端·后端·程序员
努力犯错玩AI1 小时前
微软开源TTS模型VibeVoice:一键生成90分钟超长多角色对话,告别机械音!
人工智能·后端·github