让我们有另一个答案,没有外部库。
就像你已经做过的那样,我们可以将问题分成更小的部分:
- 定义一个从字符串
all-tokens构建标记列表的函数
-
将此函数应用于输入列表中的所有字符串,并连接结果:
(mapcan #'all-tokens strings)
第一部分,获取一个状态并从中构建一个列表,看起来像一个unfold 操作(变形)。
Fold(catamorphism),在 Lisp 中称为 reduce,它从值列表和函数(以及可选的初始值)构建一个值。
双重操作unfold 接受一个值(状态)、一个函数并生成一个值列表。
对于unfold,step 函数接受一个状态并返回新状态以及结果列表。
在这里,让我们将状态定义为 3 个值:一个字符串、字符串中的起始位置以及到目前为止已解析的令牌堆栈。
我们的步进函数next-token 返回下一个状态。
;; definition follows below
(declare (ftype function next-token))
从字符串中获取所有标记的主函数只是计算一个固定点:
(defun all-tokens (string)
(do (;; initial start value is 0
(start 0)
;; initial token stack is nil
(tokens))
;; loop until start is nil, then return the reverse of tokens
((not start) (nreverse tokens))
;; advance state
(multiple-value-setq (string start tokens)
(next-token string start tokens))))
我们需要一个辅助函数:
(defun parenthesisp (c)
(find c "()"))
阶跃函数定义如下:
(defun next-token (string start token-stack)
(let ((search (position-if #'parenthesisp string :start start)))
(typecase search
(number
;; token from start to parenthesis
(when (> search start)
(push (subseq string start search) token-stack))
;; parenthesis
(push (subseq string search (1+ search)) token-stack)
;; next state
(values string (1+ search) token-stack))
(null
;; token from start to end of string
(when (< start (1- (length string)))
(push (subseq string start) token-stack))
;; next-state
(values string nil token-stack)))))
您可以尝试使用单个字符串:
(next-token "(aviyon" 0 nil)
"(aviyon"
1
("(")
如果您获取结果状态值并重用它们,您有:
(next-token "(aviyon" 1 '("("))
"(aviyon"
NIL
("aviyon" "(")
这里,第二个返回值是NIL,结束生成过程。
最后,你可以这样做:
(mapcan #'all-tokens '("(aviyon" "213" "flyingman" "no))"))
这给出了:
("(" "aviyon" "213" "flyingman" "no" ")" ")")
上面的代码并不完全通用,因为all-tokens 对next-token 了解太多:您可以重写它以获取任何类型的状态。
您还可以使用相同的机制处理字符串序列,方法是在状态变量中保留更多信息。
此外,在真正的词法分析器中,您不想反转整个标记列表,您可以使用队列来提供解析器。