IDoom3Tokenizer的getNextToken方法是一个相对复杂的实现,其工作原理就是一个有限状态机(Finite State Machine,简称FSM)。所谓有限状态机就是指状态是有限的,并且根据当前的状态,来执行某个操作。那么我们来看一下getNextToken这个有限状态机有:

  • 哪几个状态(即有限的状态数量)?
  • 每个状态的开始条件是什么?
  • 每个状态的结束条件是什么?
  • 在某个状态下要做什么(操作)?我们带着上面的问题来看一下getNextToken的源码,具体代码如下所示:
  1. public getNextToken ( tok : IDoom3Token ) : boolean {
  2. //使用as关键字将IDoom3Token 向下转型为Doom3Token类型
  3. let token : Doom3Token = tok as Doom3Token ;
  4. //初始化为空字符串
  5. let c : string = "" ;
  6. //重用token,每次调用reset函数时,将token的索引重置为0
  7. //避免发生内存重新分配
  8. token . reset ( ) ;
  9. do {
  10. // 第一步:跳过所有的空白字符,返回第一个可显示的字符
  11. //开始条件:当前字符是空白符
  12. c = this . _skipWhitespace ( );
  13. // 第二步:判断非空白字符的第一个字符是什么
  14. if ( c === '/' && this . _peekChar ( ) === '/' ) {
  15. // 开始条件:如果是//开头,则跳过单行注释中的所有字符
  16. c = this . _skipComments0 ( ) ;
  17. } else if ( c === '/' && this . _peekChar ( ) === '*' ) {
  18. //开始条件:如果是/*开头的字符,则跳过多行注释中的所有字符
  19. c = this . _skipComments1 ( ) ;
  20. } else if ( this . _isDigit( c ) || c === '-' || ( c === '.' && this . _isDigit( this . _peekChar ( ) ) ) ) {
  21. //开始条件:如果当前字符是(数字)或是(符号)或者(以点号且数字开头)
  22. //则返回到上一个字符索引处,因为第一个字符被消费掉了,而_getNumber会重新处理数字情况,这样需要恢复到数字解析的原始状态
  23. this . _ungetChar ( ) ;
  24. this . _getNumber ( token ) ;
  25. return true ;
  26. } else if ( c === '\"' || c === '\'' ) {
  27. //开始条件:如果以\"或\'开头的字符,例如'origin'或'Body'
  28. this . _getSubstring ( token , c ) ;
  29. return true ;
  30. } else if ( c.length > 0 ) {
  31. //开始条件:排除上述所有的条件并且在确保数据源没有解析完成的情况下
  32. //返回到上一个字符索引处,因为_getString会重新处理相关情况
  33. this . _ungetChar ();
  34. this . _getString ( token ) ;
  35. return true ;
  36. }
  37. } while ( c . length > 0 ) ;
  38. return false ;
  39. }

  这段代码的关键点都在注释里面,其中状态的开始条件都已标注出来。状态的结束条件都注释在对应的状态处理函数中。  我们来看一下这段代码中的向下转型相关内容。上面将IDoom3Token类型使用as操作符向下转型为Doom3Token是因为_getNumber / _getSubstring / _getString这三个方法的输出参数类型是Doom3Token类而不是IDoom3Token接口,因此需要从IDoom3Token向下转型到Doom3Token。在TypeScript也可以使用< >来进行类型转换,具体代码如下所示:

  1. let token : Doom3Token = < Doom3Token > tok ;