词法分析的核心任务是将无结构的原始字符串输入,转化为一个结构化的、线性的词元(Token)序列。每个词法单元都代表了公式中的一个最小语法单元,例如数字、操作符、括号或变量名。在此过程中,所有与语法结构无关的字符,如空格、换行符等,都会被识别并丢弃。

一个典型的词法分析器(一般取名 Lexer 或者 Tokenizer)。它会维护一个指向输入字符串的指针,并从当前位置开始,按顺序根据正则表达式进行匹配。一旦找到匹配项,它就会生成一个对应的词元对象,并将指针向前移动匹配的长度,然后开始下一轮匹配。

每个词元对象不仅包含其类型(type) 和值 (value),还必须包含精确的位置信息,如行号(line)和列号(column)。

export class Lexer {
  analyse(source: Iterator<string>) {
    const tokens: Token[] = []
    const it = new PeekIterator(source, '\0')
 
    while (it.hasNext()) {
      const c = it.next()
      if (c === '\0') {
        break
      }
      const lookahead = it.peek()
 
      if (c === ' ' || c === '\n' || c === '\r') {
        continue
      }
 
      if (c === '(' || c === ')') {
        tokens.push(new Token(TokenType.BRACKET, c))
        continue
      }
 
      if (c === '"' || c === "'") {
        it.putBack()
        tokens.push(Token.makeString(it))
        continue
      }
 
      if (AlphabetHelper.isLetter(c)) {
        it.putBack()
        const lastToken = tokens[tokens.length - 1] || null
        const newToken = Token.makeVarOrFunction(it)
        if(newToken.getType() !== TokenType.FUNCTION) {
          tokens.push(newToken)
          continue
        } else if(lastToken && lastToken.getType() !== TokenType.OPERATOR && lastToken.getValue()!=='('){
          throw new FormulaError({'errCode':ERRORCode.UNEXPECTED_FUNCTION_NAME})
        } else {
          tokens.push(newToken)
          continue
        }
      }
 
      if (AlphabetHelper.isNumber(c)) {
        it.putBack()
        tokens.push(Token.makeNumber(it))
        continue
      }
 
	  throw LexicalException.fromChar(c)
    }
    return tokens
  }
}
 
export class AlphabetHelper {
  static ptnLetter = /^[a-zA-Z]$/
  static ptnNumber = /^[0-9]$/
  static ptnLiteral = /^[_a-zA-Z0-9]$/
  static ptnOperator = /^[+\-*/><=!&|^%,]$/
 
  static isLetter(c: string): boolean {
    return AlphabetHelper.ptnLetter.test(c)
  }
  static isNumber(c: string): boolean {
    return AlphabetHelper.ptnNumber.test(c)
  }
  static isLiteral(c: string): boolean {
    return AlphabetHelper.ptnLiteral.test(c)
  }
  static isOperator(c: string): boolean {
    return AlphabetHelper.ptnOperator.test(c)
  }
}