对于每个段落的解析,依靠自动机。本文为自动机的实现者提供一些参考

自动机堆栈

  1. ....................
  2. [C][C][C] # 字符缓冲栈
  3. [T][T][T] # 数据栈
  4. [@][@] # 操作栈
  5. 1 ']' # 指示堆栈,并联自动机当做退出字符,串联当做子机下标
  6. 四个堆栈的操作原则是:“谁压入谁弹出”
  7. 堆栈通过 {..} 来表示,可以构成级联
  8. {..}
  9. {..}
  10. {..}
  11. {..}
  12. {..}
  13. 每个自动机都有方法
  14. enter(AmStack, char):AmStatus # 试图进入这个自动机,如果成功将设置堆栈
  15. eat(AmStack, char):AmStatus # 消费字符
  16. done(AmStack):void # 将字符缓冲的内容填充到数据栈
  17. 任何一个自动机每次被执行都会返回如下四个行为之一
  18. DROP # 丢弃当前堆栈
  19. CONTINUE # 继续,读取下一个字符,执行栈顶自动机的 run 方法
  20. DONE # 将弹出操作栈顶自动机,并执行它的 done 方法
  21. DONE_BACK # 执行 DONE 操作,并重新进入下一个可能的自动机

一个堆栈的数据结构

  1. buffer # 字符缓冲
  2. objs # 数据栈
  3. ams # 操作栈
  4. qcs # 退出字符
  5. sis # 指示堆栈
  6. i_obj
  7. i_am
  8. i_qc
  9. i_si
  10. candidates # 候选堆栈
  11.  

它应该支持的操作

  1. enter(Am, char): bool # 是否可以让这个自动机进入堆栈
  2. eat(char) : AmStatus # 消费字符,返回 false 表示不能消费了
  3. done : void # 调用栈顶自动机的 done
  4. close : T # 将对象堆栈清空
  5. pushAm(Am)
  6. popAm : Am
  7. peekAm : Am
  8. pushObj(T)
  9. popObj : T
  10. peekObj : T
  11. pushQc(char)
  12. popQc : char
  13. qc : char
  14. pushSi(int)
  15. popSi : int
  16. si : int
  17.  

串联自动机

串联自动机有一个固定的进入字符

  1. 假设一个堆栈状态为:
  2. [] # 字符缓冲是干净的
  3. [] ... # 数据栈
  4. [] ... # 操作栈
  5. ... # 指示堆栈
  6. enter ...
  7. #------------------------------------------------
  8. 如果符合进入字符
  9. [] # 字符缓冲内容由第一个子机决定
  10. [T] ... # 准备一个对象
  11. [&] ... # 压入自己
  12. -1 ... # 指示字符为自己第一个子机
  13. eat ...
  14. #------------------------------------------------
  15. 如果发现当前子机下标小于 0,则表示子机未被执行 enter ,那么就
  16. 将下标变成正数,并调用对应子机的 enter
  17. (注意,这里的下标是 1 base,需要转换成 0 base 使用)
  18. [] #
  19. [T] ... # 还是那个对象
  20. [&] ... # 串联自动机
  21. 1 ... # 下标变成正数,并调用子机的 enter
  22. 如果当前子机 enter 未遂,或者 eat 返回 DROP 了,那么自己也返回 DROP
  23. 如果返回的状态是 DONE 或者 DONE_BACK,
  24. 会调用当前子机的 done,并试图切换到下一个子机
  25. [] #
  26. [T] ... # 还是那个对象
  27. [&] ... # 串联自动机
  28. -2 ... # 下标指向下一个子机,并调用子机的 enter
  29. 如果没有下一个子机了,则返回 DONE | DONE_BACK
  30. done ...
  31. #------------------------------------------------
  32. 如果为 done 时,堆栈应该为
  33. [C][C]... # 缓冲可能为空也可能有字符
  34. [T] ... # 还是那个对象
  35. [&] ... # 操作栈顶应该是最后一个子
  36. 2 ... # 下标指向最后一个子机
  37. 如果没有达到最后一个子机,那么调用当前子机的 done
  38. 然后将将自己退栈
  39. []
  40. [] ... # 将 T 组合到之前的对象中
  41. [] ... # 清除自己
  42. ... # 清除了指示下标
  43.  

并联自动机

如果发现有超过一个自动机都进入了堆栈,并联自动机会依次为其构建堆栈

  1. 假设一个堆栈状态为:
  2. [] # 字符缓冲是干净的
  3. [] ... # 数据栈
  4. [] ... # 操作栈
  5. ... # 指示堆栈
  1. enter ...
  2. #------------------------------------------------
  3. 构建新堆栈:
  4. []
  5. [] # 不要准备对象
  6. [@] # 表示有子自动机进入了
  7. ']' # 自己的退出字符
  8. 如果超过一个自动机进入了,那么将堆栈变成
  9. {候选堆栈A} {候选堆栈B} -?- {母堆栈}
  10. {候选堆栈C} /
  11. ...
  1. 那么就会将自身压入母堆栈,同时也要在母堆栈标识退出字符
  2. []
  3. [] ...
  4. [+]... # 仅仅在当前堆栈压入自身
  5. ']'... # 压入自己的退出字符
  1. eat ...
  2. #------------------------------------------------
  3. 如果没有候选堆栈,则本自动机将执行选择一个候选堆栈
  4. 如果有候选堆栈,那么它的状态应该为 :
  5. []
  6. [] ...
  7. [+]...
  8. ']'...
  9. [?] # 子机设置
  10. [?] # 子机设置
  11. [@] # 某个子机
  12. ']' # 自己的退出字符
  13. 每次 eat 如果某个候选堆栈 DROP , 就移除掉它
  14. 保证每次都给所有的候选堆栈消费字符,如果某个堆栈 DONE 了,
  15. 则将这个堆栈关闭后得到对象组合到母堆栈中。
  16. done ...
  17. #------------------------------------------------
  18. 如果没有候选堆栈,那么就什么也不做
  19. 如果有多个候选堆栈,并联自动机会首先调用候选A的栈顶自动机的 done
  20. {候选堆栈A}.close() => T
  21. {self}.mergeHead(T)
  22. {self}.pushQc({候选堆栈A}.popQc())
  23. 并将其压入自己所在堆栈,否则,就会调用自己堆栈栈顶自动机的 done
  24. 让自己的的堆栈状态为:
  25. [] # 字符缓冲
  26. [] ... # 这个是自己的对象
  27. [+] ... # 头部就只有自己
  28. ']' ... # 自己的退出字符在顶部
  29. 执行堆栈的真正弹出
  30. []
  31. [] ... # 将 T 组合到之前的对象中
  32. [] ... # 清除了自动机
  33. ... # 清除了退出字符

原文:

http://nutzam.com/zdoc/parsing_am.html