Совместимость программ ввода-вывода SBCL - PullRequest
0 голосов
/ 27 января 2019

У меня есть программа, из которой я хочу читать, скажем, ее Python. Итак, у меня есть эти 2 функции:



(defun start-python ()
  (let ((process 
     (sb-ext:run-program "/usr/bin/python" nil
                 :output :stream
                 :input :stream
                 :wait nil
                 :search t
                 :error *standard-output*)))
    process))

(defun test-process-stream ()
  (let ((process (start-python)))
    (format (sb-ext:process-input process) "print 'hello world!'~%")
    (finish-output (sb-ext:process-input process))
    ;;I do not want to call "close" because I will be still reading from the input
    (close (sb-ext:process-input process))
    (print (read-line (sb-ext:process-output process)))
    (when (listen (sb-ext:process-output process))
      (print (read-line (sb-ext:process-output process))))

    (close (sb-ext:process-output process))
    (sb-ext:process-close process)
    ))

Я хочу, чтобы можно было постепенно читать из вывода процесса python и в то же время предоставлять вход для него. Я пробовал с несколькими методами, даже упомянутыми здесь: SBCL: Сбор выходных данных процесса запуска программы при выполнении

Но я не смог сделать это в SBCL. В коде примера я вызываю close, потому что это единственный способ получить какой-либо вывод вообще. В противном случае он просто зависает.

Я был бы очень признателен за любые указатели, потому что я застрял в этом. Я даже пытался с (listen ...) и (finish-output ...), и все равно он зависает на (read-line ...). Единственная разница с (listen ...) в том, что он возвращает false и ничего не печатается. Я даже попробовал (sleep 2), прежде чем пытаться читать. Все еще ничего.

РЕДАКТИРОВАТЬ: В конечном итоге моя цель - запустить swipl, который является SWI-Prolog. Я использовал Python здесь в качестве примера. Я хочу добиться совместимости между lisp и прологом, чтобы я мог выдавать запросы к прологу и читать обратные ответы. В настоящее время я не могу найти ни одного проекта или библиотеки, которые бы подходили для моих нужд, поэтому я пытаюсь это сделать.

Ответы [ 2 ]

0 голосов
/ 27 января 2019

Мне удалось заставить это работать со следующим кодом:

    (defun start-python ()
      (let ((process 
         (sb-ext:run-program "/usr/bin/python3" nil
                     :output :stream
                     :input :stream
                     :wait nil
                     :pty t
                     :error *standard-output*)))
        process))

    (defun read-until-newline (process)
      (let ((r ""))
        (loop for c = (read-char-no-hang (sb-ext:process-pty process))
           do (progn
            (if (or (not c) (char= c #\newline))
            (return-from read-until-newline r)
            (setf r (concatenate 'string r (format nil "~c" c))))))))

    (defun print-all-output (process &key (discard nil))
      (sleep 0.1)
      (loop 
         do (progn
          (if (listen (sb-ext:process-pty process))
              (if (not discard)
                  (print (read-until-newline process))
                  (read-until-newline process))
              (return)))))

    (defun send-to-python (process str)
      (format (sb-ext:process-pty process) str)
      (finish-output (sb-ext:process-pty process)))

    (defun test-process-stream ()
      (let* ((process (start-python)))
        (print-all-output process :discard t) ;;discard banner message
        (send-to-python process "X=[1,2,3,4,5]~%print(X[:2],X[2:])~%X~%")
        (print-all-output process)
        (sb-ext:process-close process)
        ))

Большое спасибо @ jkiiski за помощь в отладке этого фрагмента кода.Хитрость заключалась в том, чтобы использовать :pty в качестве аргумента для run-program, а затем использовать поток (sb-ext:process-pty process) для связи с процессом.После этого и выполнение (finish-output (sb-ext:process-pty process)) сбрасывает наш ввод в программу.Тогда важно немного подождать, чтобы подпроцесс мог накапливать выходные данные.После (sleep 0.1), (listen ...) сможет определить, есть ли ожидающий вывод.Тогда это просто цикл с (read-char-no-hang) для чтения символов, пока не останется ни одного.Я разделил вывод новой строкой, как показано в (read-until-newline) Приведенный выше код дает следующий вывод:

">>> [1, 2] [3, 4, 5]^M" 
">>> [1, 2, 3, 4, 5]^M" 
">>> "

Любой последующий вызов (print-all-output process) будет выводить вывод программы постепенно.

0 голосов
/ 27 января 2019

[Большая часть этого ответа не интересна, потому что спрашивающий спросил о Python, когда выяснилось, что они имели в виду Пролог, поэтому я потратил впустую свое время на решение проблемы, которую они сказали, а не той, которая у них была на самом деле. Я оставляю это здесь на случай, если что-то из этого будет полезно кому-либо еще.]

Я не думаю, что это проблема с SBCL. Учитывая следующий код:

(defun call-with-process (f program args &rest keys &key &allow-other-keys)
  (let ((process (apply #'sb-ext:run-program program args keys)))
    (unwind-protect
        (funcall f process)
      (sb-ext:process-close process))))

(defmacro with-process ((process program args &rest keys &key &allow-other-keys)
                        &body forms)
  `(call-with-process
    (lambda (,process)
      ,@forms)
    ,program ,args ,@keys))

(defun test-echo-process (&rest strings-to-send)
  (with-process (p "/bin/cat" '()
                   :wait nil
                   :input ':stream
                   :output ':stream)
    (let ((i (sb-ext:process-input p))
          (o (sb-ext:process-output p)))
      (dolist (s strings-to-send)
        (format i "~A~%" s)
        (finish-output i)
        (format t "Sent ~A, got ~A~%" s (read-line o))))))

Тогда test-echo-process отлично работает.

Но эта функция (эквивалентная вашей) зависает:

(defun test-python-process ()
  (with-process (p "/usr/bin/python" '()
                   :wait nil
                   :input ':stream
                   :output ':stream)
    (let ((i (sb-ext:process-input p))
          (o (sb-ext:process-output p)))
      (format i "print 'here'~%")
      (finish-output i)
      (format t "~A~%" (read-line o)))))

Итак, на самом деле проблема в том, как ведет себя Python. И вы можете продемонстрировать это на самом деле. Вот некоторый вывод из терминала:

$ cat | python
print "hi"
print "there"
hi
there
$

Что не отображается, так это то, что после того, как я набрал вторую команду print, я отправил EOF (т.е. ^ D в Unix).

Таким образом, Python, я думаю, вполне разумно буферизует свой ввод, вероятно, только в том случае, если это не терминал.

Так что вам нужно что-то сделать, чтобы это не произошло. Сначала я поместил программу, которую вы хотите, чтобы Python запускал, в файл, чтобы стандартный ввод выполнял только одно. Но тогда вы окажетесь в мире боли.

Если вы реализуете эту функцию

(defun test-python-script (args &rest strings-to-send)
  (with-process (p "/usr/bin/python" args
                   :wait nil
                   :input ':stream
                   :output ':stream)
    (let ((i (sb-ext:process-input p))
          (o (sb-ext:process-output p)))
      (dolist (s strings-to-send)
        (format i "~A~%" s)
        (finish-output i)
        (format t "sent ~A, got ~A~%" s (read-line o))))))

Тогда вы можете подумать, что немного Python в echo.py выглядит так:

from sys import stdin, exit

if __name__ == '__main__':
    for l in stdin:
        print "got " + l.strip()
    exit(0)

, а затем запуск (test-python-script '("echo.py") "foo" "bar") будет работать. Но вы были бы неправы по крайней мере двумя способами (вы можете проверить это, просто запустив python echo.py в командной строке и увидев, что он все еще буферизируется.

Первый способ, которым вы ошибаетесь, заключается в том, что использование файлов в качестве итераторов в Python имеет встроенную буферизацию, которую вы, похоже, не сможете избежать. Вы можете справиться с этим, написав небуферизованный итератор, поэтому echo.py теперь

from sys import stdin, exit

class UnbufferedLineIterator(object):
    # I take back what I said about 'perfectly reasonably'
    def __init__(self, stream):
        self.stream = stream

    def __iter__(self):
        return self

    def next(self):
        line = self.stream.readline()
        if len(line) > 0:
            return line
        else:
            raise StopIteration

if __name__ == '__main__':
    for l in UnbufferedLineIterator(stdin):
        print "got " + l.strip()
    exit(0)

И это может сработать, но это не сработает, потому что все еще буферизует где-то на стороне Python. Вы можете избавиться от этого очень грубо, запустив Python с аргументом -u. Итак, наконец

* (test-python-script '("-u" "echo.py") "foo" "bar")
sent foo, got got foo
sent bar, got got bar

Однако я думаю, что реальный ответ - пойти и спросить людей Python, как это должно работать, потому что я не могу поверить, что -u правильный ответ или что это может быть так сложно.

...