Давайте ответим еще, без внешних библиотек.Как вы уже сделали, мы можем разбить задачу на более мелкие части:
- определить функцию, которая создает список токенов из строки,
all-tokens
применитьэта функция на всех строках в вашем входном списке и объединяет результат:
(mapcan #'all-tokens strings)
Первая часть, берущая состояние и строящая из него список, выглядит как unfold
операция (анаморфизм).
Fold (катаморфизм), называемый reduce
в Лиспе, строит значение из списка значений и функцию (и, необязательно, начальное значение).Двойная операция, unfold
, принимает значение (состояние), функцию и генерирует список значений.В случае unfold
, функция шага принимает состояние и возвращает новое состояние вместе с результирующим списком.
Здесь давайте определим состояние как 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
: выможет переписать его, чтобы принять любое состояние.Вы также можете обрабатывать последовательности строк, используя тот же механизм, сохраняя больше информации в вашей переменной состояния.Кроме того, в реальном лексере вы бы не хотели переворачивать весь список токенов, вы бы использовали очередь для подачи синтаксического анализатора.