You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言词法分析器执行异常:else分支提前触发问题排查

词法分析器异常问题排查

问题描述

我用Go语言实现了一个词法分析器,输入内容为+- */。预期程序会先识别+、-、*、/等合法Token,再处理未知Token,但实际执行时,else分支的未知Token提示被优先输出,且代码中println(token.text, token.kind, "token 119")语句未按预期打印有效内容。

实现代码

// Invalid token found, print error message and exit.
func abort(message string) {
    panic(message)
}

// Skip whitespace except newlines, which we will use to indicate the end of a statement.
func (s *Source) skipWhitespace() {
    for s.curChar == " " || s.curChar == "\t" || s.curChar == "\r" {
        s.nextChar()
    }
}

// Skip comments in the code.
func skipComment() {}

// Return the next token.
func (s *Source) getToken() Token {
    var token Token
    // Check the first character of this token to see if we can decide what it is.
    // If it is a multiple character operator (e.g., !=), number, identifier, or keyword then we will process the rest.
    // s.skipWhitespace()
    println("curChar: " + s.curChar)
    s.skipWhitespace()
    println("curChar: after 84 " + s.curChar)

    if s.curChar == "+" {
        token = Token{s.curChar, PLUS}
    } else if s.curChar == "-" {
        token = Token{s.curChar, MINUS}
    } else if s.curChar == "*" {
        token = Token{s.curChar, ASTERISK}
    } else if s.curChar == "/" {
        token = Token{s.curChar, SLASH}
    } else if s.curChar == "\n" {
        token = Token{s.curChar, NEWLINE}
    } else if s.curChar == "\000" {
        token = Token{s.curChar, EOF}
    } else {
        println("Unknown token: " + s.curChar)
    }
    s.nextChar()

    return token

}

// Process the next character.
func (s *Source) nextChar() {
    s.curPos++
    if s.curPos >= len(s.source) {
        s.curChar = "\000" // EOF
    } else {
        s.curChar = string(s.source[s.curPos])
    }

}

func do_lexing(codeString string) { // code_string = `+- */`
    // Initialize the source.
    source := Source{source: codeString + "\n", curChar: "", curPos: -1}
    // Loop through and print all tokens.
    token := source.getToken()
    println(token.text, token.kind, "token 119")

    for token.kind != EOF {
        println(token.text, token.kind)

        token = source.getToken()

    }
}

实际输出结果

curChar: 
curChar: after 84 
Unknown token: 
 0 token 199
 0
curChar: +
curChar: after 84 +
+ 202
curChar: -
curChar: after 84 -
- 203
curChar:  
curChar: after 84 *
* 204
curChar: /
curChar: after 84 /
/ 205
curChar: 

curChar: after 84 


 0
curChar: 
curChar: after 84 

问题原因与修复方案

核心问题

  1. 初始化未加载第一个有效字符
    在do_lexing函数中,Source初始化时curPos设为-1,curChar为空字符串。第一次调用getToken时,直接处理空字符:

    • 空字符不在skipWhitespace的跳过列表中,跳过逻辑不执行
    • 进入else分支打印未知Token提示
    • 之后调用nextChar()才将curChar设置为输入的第一个字符+
      这导致第一次getToken返回零值Token,出现开头的无效输出。
  2. 打印顺序符合代码执行流程
    代码中先调用getToken()(内部包含println),再打印token 119,所以getToken里的打印会先出现,这是正常执行顺序,但因为第一次getToken拿到无效Token,导致token 119打印内容异常。

修复方案

  • 初始化时加载第一个字符
    在do_lexing中初始化Source后,先调用一次nextChar(),确保curChar指向输入的第一个有效字符:

    func do_lexing(codeString string) {
        source := Source{source: codeString + "\n", curChar: "", curPos: -1}
        source.nextChar() // 初始化第一个有效字符
        token := source.getToken()
        println(token.text, token.kind, "token 119")
        // 剩余循环逻辑保持不变
    }
    
  • 调整getToken的处理顺序
    将skipWhitespace()移到打印当前字符之前,确保先跳过空格再处理有效字符:

    func (s *Source) getToken() Token {
        var token Token
        s.skipWhitespace() // 先跳过空格
        println("curChar: " + s.curChar)
        println("curChar: after skip " + s.curChar)
        // 后续Token判断逻辑保持不变
    }
    
  • 完善else分支的Token赋值
    在else分支中给token设置明确的未知类型,避免返回零值:

    } else {
        println("Unknown token: " + s.curChar)
        token = Token{s.curChar, UNKNOWN} // 需提前定义UNKNOWN常量
    }
    

内容的提问来源于stack exchange,提问作者Farhan Ali Raza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 22:55:14