Нетривиальный Repl

Sergey ShishkinSergey Shishkin
7 min read

Это плавный переход от eval к loop к циклам и где уместно вспомнить, что эта тема уже затрагивалась в контексте самой часто употребляемой функции for и рекурсии. Repl как функция не анонсирована в документации, но есть авторская классификация в файлах репозитария, где кроме неё указаны ещё две функции, которые в документации есть. Это remark и complete. Естественно, что к функции нет формы обращения, но зато на ееё примере, мы, наконец, приведем полное описание определения функции. Кроме самого кода определения полный список зависимостей, а это, все-таки не только функции, но и глобальные параметры. Кстати, у меня есть точка зрения на функции как на параметры, но это отдельная тема. Итак код опреления

(de repl (Exe (i8* . Prmt) X)
   (if (and (symb? X) (== (firstByte X) (char "-")))
      (let E (save (parse (xName X) YES (hex "5D0A") 0))  # '\n', ']', EOF
         (evList E) )
      (let
         (Lnk (val $NsLink)
            Tr1 (save (val $Transient))
            Tr2 (save (val 2 $Transient))
            Pr1 (save (val $PrivT))
            Pr2 (save (val 2 $PrivT))
            V (link (push -ZERO NIL))
            Raw (val Termio) )
         (save (val $Intern))
         (set $NsLink (val $Link))
         (when (nil? X)
            (setCooked)
            (unless (val $Repl)
               (set $Repl YES)
               (iSignal (val SIGINT Sig) (fun sig)) ) )
         (rdOpen Exe X (b8+ (ioFrame T)))
         (set $PrivT (set 2 $PrivT $Nil))
         (set $Rule (set $Transient (set 2 $Transient $Nil)))
         (setq X $Nil)
         (if (== (val $InFile) (val (val $InFiles)))  # Stdin
            (until (nil? (stdRead Prmt))
               (let Y (set V @)
                  (setq X
                     (if (and (=0 (val $Chr)) Prmt)
                        (stdEval Y)
                        (eval Y) ) ) ) )
            (until (nil? (read1 0))
               (setq X (eval (set V @))) ) )
         (popInFiles)
         (set $Intern (val (val $NsLink)))
         (when Raw
            (setRaw) )
         (set 2 $PrivT Pr2)
         (set $PrivT Pr1)
         (set 2 $Transient Tr2)
         (set $Transient Tr1)
         (set $NsLink Lnk)
         X ) ) )

https://picolisp-manual.tiddlyhost.com/#repl

А теперь комментарии

  1. Определение функции repl:

     Copy(de repl (Exe (i8* . Prmt) X)
    

    Функция repl принимает три аргумента: Exe, Prmt и X. Exe — это исполняемый файл, Prmt — это подсказка (prompt), а X — это входное выражение.

  2. Проверка символа:

     Copy(if (and (symb? X) (== (firstByte X) (char "-")))
    

    Если X является символом и начинается с "-", то выполняется следующий блок кода.

  3. Обработка символа:

     Copy(let E (save (parse (xName X) YES (hex "5D0A") 0))
        (evList E))
    

    Здесь происходит разбор и выполнение выражения X. parse разбирает имя символа, а evList выполняет список выражений.

  4. Основной блок REPL:

     Copy(let
        (Lnk (val $NsLink)
           Tr1 (save (val $Transient))
           Tr2 (save (val 2 $Transient))
           Pr1 (save (val $PrivT))
           Pr2 (save (val 2 $PrivT))
           V (link (push -ZERO NIL))
           Raw (val Termio))
    

    Здесь сохраняются текущие значения различных системных переменных и создается новая переменная V.

  5. Настройка окружения:

     Copy(save (val $Intern))
     (set $NsLink (val $Link))
     (when (nil? X)
        (setCooked)
        (unless (val $Repl)
           (set $Repl YES)
           (iSignal (val SIGINT Sig) (fun sig))))
    

    Сохраняются текущие значения и настраивается окружение для выполнения REPL. Если X равен nil, то устанавливается обработчик сигнала SIGINT.

  6. Чтение и выполнение выражений:

     Copy(rdOpen Exe X (b8+ (ioFrame T)))
     (set $PrivT (set 2 $PrivT $Nil))
     (set $Rule (set $Transient (set 2 $Transient $Nil)))
     (setq X $Nil)
    

    Открывается поток ввода и настраиваются переменные окружения.

  7. Цикл чтения и выполнения:

     Copy(if (== (val $InFile) (val (val $InFiles)))
        (until (nil? (stdRead Prmt))
           (let Y (set V @)
              (setq X
                 (if (and (=0 (val $Chr)) Prmt)
                    (stdEval Y)
                    (eval Y)))))
        (until (nil? (read1 0))
           (setq X (eval (set V @)))))
    

    В зависимости от источника ввода (стандартный ввод или файл), выполняется цикл чтения и выполнения выражений.

  8. Восстановление окружения:

     Copy(popInFiles)
     (set $Intern (val (val $NsLink)))
     (when Raw
        (setRaw))
     (set 2 $PrivT Pr2)
     (set $PrivT Pr1)
     (set 2 $Transient Tr2)
     (set $Transient Tr1)
     (set $NsLink Lnk)
     X
    

    Восстанавливаются сохраненные значения системных переменных и возвращается результат выполнения последнего выражения.

И, наконец, список зависимостей

  1. Системные переменные:

    • $NsLink: Используется для хранения и восстановления текущего пространства имен.

    • $Transient: Используется для временных данных.

    • $PrivT: Используется для приватных данных.

    • $Intern: Используется для внутренних данных.

    • $Repl: Флаг, указывающий, что REPL активен.

    • $InFile: Указывает на текущий входной файл.

    • $InFiles: Список входных файлов.

    • $Chr: Текущий символ.

    • $Link: Текущая связь.

    • $Rule: Правила, используемые в окружении.

  2. Функции:

    • symb?: Проверяет, является ли объект символом.

    • firstByte: Возвращает первый байт символа.

    • save: Сохраняет текущее значение переменной.

    • parse: Разбирает строку или символ.

    • evList: Выполняет список выражений.

    • setCooked: Устанавливает режим cooked для ввода.

    • iSignal: Устанавливает обработчик сигнала.

    • rdOpen: Открывает поток ввода.

    • ioFrame: Создает фрейм ввода-вывода.

    • stdRead: Читает стандартный ввод.

    • stdEval: Выполняет выражение из стандартного ввода.

    • eval: Выполняет выражение.

    • read1: Читает одно выражение.

    • popInFiles: Удаляет текущий входной файл из стека.

    • setRaw: Устанавливает режим raw для ввода.

По пути и прокомментируем код и других двух, без указания зависимостей, поскольку их код, все-таки, не такой большой.

Этот код на языке PicoLisp определяет функцию remark, которая, вероятно, используется для аннотирования или комментирования данных. Давайте разберем его по частям:

  1. Определение функции remark:

     Copy(de remark ("X")
    

    Функция remark принимает один аргумент "X", который может быть числом, символом или списком.

  2. Локальная переменная Lst:

     Copy(let? Lst
    

    Объявляется локальная переменная Lst, которая будет использоваться для хранения результатов.

  3. Рекурсивная функция recur:

     Copy(recur ("X")
    

    Внутри remark определена рекурсивная функция recur, которая также принимает один аргумент "X".

  4. Создание списка с помощью make:

     Copy(make
        (cond
    

    Используется функция make для создания списка на основе условий.

  5. Условия для чисел:

     Copy((num? "X")
        (when (>= 799999 "X" 700000)
           (link (dat$ "X" "-")) )
        (unless (=0 *Scl)
           (link (format "X" *Scl)) ) )
    

    Если "X" является числом, то:

    • Если число находится в диапазоне от 700000 до 799999, добавляется элемент с использованием функции dat$.

    • Если переменная *Scl не равна нулю, добавляется отформатированное значение "X" с учетом *Scl.

  6. Условия для символов:

     Copy((sym? "X")
        (let? Nsp (nsp "X")
           (or
              (== 'pico Nsp)
              (== 'priv Nsp)
              (link (pack (sym Nsp) "~" (sym "X"))) ) )
        (when (type "X")
           (link (sym @)) ) )
    

    Если "X" является символом, то:

    • Определяется пространство имен Nsp символа "X" с помощью функции nsp.

    • Если пространство имен равно 'pico или 'priv, ничего не добавляется.

    • В противном случае добавляется упакованная строка, состоящая из символа пространства имен, символа ~ и символа "X".

    • Если у символа есть тип, добавляется символ этого типа.

  7. Условие по умолчанию:

     Copy(T (and (recurse (car "X")) (chain @)))
    

    Если "X" не является ни числом, ни символом, то предполагается, что это список. В этом случае рекурсивно обрабатывается первый элемент списка (car "X"), и результат добавляется в цепочку.

  8. Вывод результата:

     Copy(prin
        "  "
        (and (=1 (%@ "isatty" 'I (fd))) "\e[0;36m")
        "#" )
     (for X Lst
        (prin " " X) )
     (when (=1 (%@ "isatty" 'I (fd)))
        (prin "\e[0m") )
    
    • Выводится строка с отступом и символом #.

    • Если стандартный ввод является терминалом, добавляется ANSI-код для изменения цвета текста на голубой.

    • Затем выводятся все элементы списка Lst.

    • Если стандартный ввод является терминалом, сбрасывается цвет текста с помощью ANSI-кода.

Таким образом, функция remark обрабатывает входные данные, создавая список аннотаций или комментариев, и выводит их в формате, который может включать цветовое оформление, если вывод направлен в терминал.

  1. Определение функции complete:

     Copy(de complete (S)
    

    Функция complete принимает один аргумент S, который, вероятно, является строкой или символом.

  2. Проверка аргумента S:

     Copy(when S
    

    Если S не равен nil (то есть, если S имеет значение), выполняется следующий блок кода.

  3. Установка значения переменной *Cmpl:

     Copy(setq "*Cmpl"
        (if (=T S)
           (list "   ")
           (flip (all* S))
        )
     )
    

    Здесь происходит проверка значения S:

    • Если S равно T (истина), то переменной *Cmpl присваивается список, содержащий строку из трех пробелов " ".

    • В противном случае, переменной *Cmpl присваивается результат выполнения функции flip над результатом функции all*, примененной к S.

  4. Функции all* и flip:

    • all*: Эта функция, вероятно, возвращает список всех возможных дополнений или вариантов для строки S. Точное поведение зависит от реализации, но обычно это может быть поиск по шаблону или регулярному выражению.

    • flip: Эта функция, вероятно, изменяет порядок элементов в списке на обратный.

  5. Извлечение значения переменной *Cmpl:

     Copy(pop '"*Cmpl")
    

    Функция pop извлекает и возвращает значение переменной *Cmpl. В PicoLisp pop обычно используется для извлечения значения из списка или стека, но в данном случае она используется для извлечения значения переменной.

Таким образом, функция complete устанавливает значение переменной *Cmpl в зависимости от входного аргумента S и затем извлекает это значение. Если S равно T, то возвращается список с тремя пробелами, иначе возвращается перевернутый список всех возможных дополнений для S.

0
Subscribe to my newsletter

Read articles from Sergey Shishkin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sergey Shishkin
Sergey Shishkin

Всегда чему-то учусь!