[ИУ9] Основы информатики

Домашнее задание №5

Нужно дополнить интерпретатор как минимум тремя из описанных ниже конструкций на выбор студента. За каждое выполненное расширение (с отдельным подзаголовком) ставится по одному баллу.

Все расширения интерпретатора опциональны. Чтобы тестирующий бот смог проверить корректность расширения, в исходный текст программы (в произвольное место, кроме комментария) нужно добавить символ с именем feature-***. Это можно сделать например так:

(define feature-nested-if #t)

или так:

'feature-nested-if

или даже так:

(define feature-nested-if
  (list (test (interpret #(0 if 1 if 2 endif 3 endif 4) '()) '(4))
        (test (interpret #(1 if 2 if 3 endif 4 endif 5) '()) '(5 4 3))))

(run-tests feature-nested-if)

Имена расширений указаны в скобках в подзаголовках.

В комментариях символы feature-*** будут проигнорированы.

if с альтернативной веткой (feature-if-else)

Предлагается добавить ветку else к оператору if. Примеры:

(interpret #(1 if 100 else 200 endif) '())         '(100)
(interpret #(0 if 100 else 200 endif) '())         '(200)

Вложенные if (feature-nested-if)

Внутри оператора if … endif допустимы вложенные операторы if … endif. Примеры:

(interpret #(0 if 1 if 2 endif 3 endif 4) '())     '(4)
(interpret #(1 if 2 if 3 endif 4 endif 5) '())     '(5 4 3)
(interpret #(1 if 0 if 2 endif 3 endif 4) '())     '(4 3)

Цикл с предусловием while … wend (feature-while-loop)

Слово while работает аналогично слову if:

Слово wend передаёт управление на предшествующее слово while.

Таким образом, цикл продолжается до тех пор, пока слово while не снимет со стека 0.

Примеры:

(interpret #(while wend) '(3 7 4 0 5 9))          '(5 9)

(interpret #(define sum
               dup
               while + swap dup wend
               drop
             end
             1 2 3 0 4 5 6 sum)
           '())                                   '(15 3 2 1)

(interpret #(define power2
               1 swap dup
               while
                 swap 2 * swap 1 - dup
               wend
               drop
             end
             5 power2 3 power2 power2) '())       '(256 32)

Цикл должен допускать вложение, если реализован вложенный if.

Цикл с постусловием repeat … until (feature-repeat-loop)

Слово repeat ничего не делает.

Слово until снимает со стека число. Если оно нулевое, управление передаётся на предшествующее слово repeat, иначе — на следующую инструкцию.

Таким образом, цикл делает всегда как минимум одну итерацию.

Если реализован вложенный if, цикл repeat … until также должен допускать вложение.

Цикл с параметром for … next (feature-for-loop)

Цикл for в FORTH вызывается, как правило, так:

 for … next : ... ‹от› ‹до›  →  ...

Т.е. цикл снимает со стека два числа ‹от› и ‹до› и повторяет тело цикла для всех чисел в диапазоне ‹от›‹до› включительно, т.е. ‹до› − ‹от› + 1 раз.

Внутри цикла можно пользоваться словом i, которое кладёт на стек текущее значение счётчика цикла. (Вне цикла результат работы этого слова не определён.)

Текущее и конечное значение счётчика цикла рекомендуется хранить на стеке возвратов (см. книжку Баранова и Ноздрунова). Таким образом, внутри цикла for … next нельзя будет использовать слово exit.

Слово for снимает со стека данных и кладёт на стек возвратов значения ‹до› (подвершина стека возвратов) и ‹от› (вершина стека возвратов).

Слово i кладёт на стек данных содержимое верхушки стека возвратов, стек возвратов не меняется.

Слово next декрементирует вершину стека возвратов и сравнивает её с подвершиной:

Цикл for … next вложенным можно не реализовывать. (В реализациях FORTH во вложенных циклах со счётчиком слово j позволяет получить значение счётчика внешнего цикла.)

Пример.

(interpret #(define fact
               1 1 rot for i * next
             end
             6 fact 10 fact)
           '())                                    (3628800 720)

Вместо стека возвратов можно создать третий стек специально для цикла for … next.

Операторы break и continue (feature-break-continue)

(Один балл за оба.)

Слово break прерывает выполнение цикла — выполняет переход на слово, следующее за словом-окончанием цикла (wend, repeat или next).

Слово continue завершает текущую итерацию цикла — выполняет переход на слово-окончание цикла (wend, repeat или next).

Конструкция switch-case (feature-switch-case)

Синтаксис:

switch
…
case ‹КОНСТ1›  … exitcase …
…
case ‹КОНСТn›  … exitcase …
…
endswitch

После слова case должна располагаться целочисленная константа.

Семантика идентична семантике switch в Си.

Таким образом, слово exitcase эквивалентно break внутри switch в Си, через метки, как и в Си, можно «проваливаться».

Аналог метки default можно не реализовывать. Вложенные switch тоже можно не реализовывать.

Статьи высшего порядка — косвенный вызов и лямбды (feature-hi-level)

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

Синтаксис и семантика.

Пример. Слово power применяет функцию указанное количество раз:

(interpret #(define power
               ; power : x λ n ≡ λ(λ(λ…λ(x)…)) (n раз)
               dup 0 = if drop drop exit endif
               rot                               ; n  λ  x
               over                              ; n  λ  x  λ
               apply                             ; n  λ  x′
               rot                               ; x′ λ  n
               1 -                               ; x′ λ  n−1
               power                             ; рекурсивный вызов
             end
             define square dup * end
             3 & square 3 power                  ; ((3²)²)² = 6561
             2 lam dup dup * * endlam 2 power    ; (2³)³ = 512
            )
           '())                                    (512 6561)

Безымянные статьи lam … endlam могут быть вложенными.

Хвостовая рекурсия (feature-tail-call)

Слово tail ‹имя› осуществляет вызов определённого пользователем слова ‹имя› без помещения адреса следующего слова на стек возвратов. Таким образом, остаток предыдущей статьи игнорируется (на него возврата не будет), вызов tail ‹имя› в некотором смысле эквивалентен goto, где роль метки играет определение статьи.

Поведение tail ‹имя› эквивалентно ‹имя› exit с единственным отличием, что первое не заполняет стек возвратов.

Пример.

(interpret #(define F 11 22 33 tail G 44 55 end
             define G 77 88 99 end
             F)
           '())                                   (99 88 77 33 22 11)
(interpret #(define =0? dup 0 = end
             define gcd
                 =0? if drop exit endif
                 swap over mod
                 tail gcd
             end
             90 99 gcd
             234 8100 gcd) '())                   (18 9)

Глобальные переменные (feature-global)

Пример.

(interpret #(defvar counter 0
             define nextnum
               counter dup 1 + set counter
             end
             nextnum nextnum
             nextnum nextnum +
             nextnum nextnum *)
           '())                                   (20 5 1 0)