Existe uma maneira idiomática de ler cada linha em um buffer para processá-la linha por linha?

11

No Python, eu faria o seguinte para processar um arquivo linha por linha:

with open(infile) as f:
    for line in f:
        process(line)

Tentando pesquisar como fazer o mesmo no elisp (com buffers em vez de arquivos), não encontrei uma maneira óbvia.

(Quero terminar com duas estruturas de dados ordenadas de linhas, uma com todas as linhas correspondentes a uma expressão regular, a outra contendo aquelas que não corresponderam.)

The Unfun Cat
fonte

Respostas:

23

Existem várias maneiras de fazer isso. O caminho de Kaushal pode ser um pouco mais eficiente, com:

(goto-char (point-min))
(while (not (eobp))
  (let ((line (buffer-substring (point)
                                (progn (forward-line 1) (point)))))
    ...))

Porém, no Emacs, é muito mais usual trabalhar no buffer do que nas strings. Então, em vez de extrair a string e depois trabalhar nela, basta:

(goto-char (point-min))
(while (not (eobp))
  ...
  (forward-line 1))

Além disso, se você deseja operar em uma região e não em todo o buffer, e se o seu "operar" inclui a modificação do buffer, é frequente fazê-lo ao contrário (para que você não seja mordido pelo fato de o "fim" "a posição da sua região se move toda vez que você modifica o buffer):

(goto-char end)
(while (> (point) start)
  ...
  (forward-line -1))
Stefan
fonte
Obrigado por essas dicas de otimização! Sempre bom aprender com você.
precisa saber é o seguinte
Sobre o último trecho, deve ser assim (let ((start (point))) (goto-char (point-max)) (while (> (point) start) ... (forward-line -1))):?
Kaushal Modi 14/01
Não, o último trecho apenas assume que starte endsão variáveis ​​existentes que delimitam a região na qual queremos operar.
Stefan
6

Não conheço nenhuma maneira idiomática, mas vim com isso:

(defun my/walk-line-by-line ()
  "Process each line in the buffer one by one."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (not (eobp))
      (let* ((lb (line-beginning-position))
             (le (line-end-position))
             (ln (buffer-substring-no-properties lb le)))
        (message ">> %s" ln) ; Replace this with any processing function you like
        (forward-line 1)))))
Kaushal Modi
fonte
1

Eu acho que o seguinte é o mais idiomático possível:

(dolist (line (split-string (buffer-string) "\n")) 
  ... process line here ...
  )

EDIT: Aqui está outra solução loopno lugar de dolist, e que também classifica as linhas de acordo com a correspondência ou não com sua expressão regular:

(loop for line in (split-string (buffer-string) "\n")
  if (string-match "your-regexp" line)
    collect line into matching
  else
    collect line into nonmatching
  finally return (cons matching nonmatching)
  )

Se você definir uma variável para a saída desta função, digamos (setq x (loop ...)), a lista desejada de linhas correspondentes será encontrada (car x), com a lista de linhas não correspondentes (cdr x).

Ruy
fonte