Нужно дополнить интерпретатор как минимум тремя из описанных ниже конструкций на выбор студента. За каждое выполненное расширение (с отдельным подзаголовком) ставится по одному баллу.
Все расширения интерпретатора опциональны. Чтобы тестирующий бот смог проверить
корректность расширения, в исходный текст программы (в произвольное место,
кроме комментария) нужно добавить символ с именем 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
:
0
— передаёт управление на оператор, следующий за wend
,Слово 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
декрементирует вершину стека возвратов и сравнивает её
с подвершиной:
next
,for
.Цикл 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
в Си.
switch
снимает со стека число, ищет метку case
с заданной
константой (если нашлось несколько меток с одинаковой константой, поведение
не определено) и переходит на неё. Если константа не найдена, осуществляется
переход на слово после endswitch
.case
осуществляет переход на слово после метки.exitcase
осуществляет переход на слово после endswitch
.endswitch
ничего не делает.Таким образом, слово exitcase
эквивалентно break
внутри switch
в Си,
через метки, как и в Си, можно «проваливаться».
Аналог метки default
можно не реализовывать. Вложенные switch
тоже можно
не реализовывать.
feature-hi-level
)Предлагается добавить в интерпретатор поддержку статей высшего порядка, аналог функций/процедур высшего порядка в других языках программирования.
Синтаксис и семантика.
& ‹имя›
требует после себя имя статьи, определённой пользователем,
при выполнении оставляет на стеке данных адрес статьи — номер слова,
на который выполнился бы переход при обычном вызове слова. Адрес встроенной
статьи получить нельзя.lam … endlam
определяет безымянную статью:
lam
помещает на стек данных адрес слова, следующего за словом lam
и осуществляет переход на слово, следующее за endlam
,endlam
снимает адрес со стека возвратов и осуществляет на него переход —
аналогично словам end
и exit
.apply
— снимает со стека данных адрес статьи и осуществляет её вызов —
кладёт на стек возвратов номер слова, следующего за apply
и осуществляет
переход на слово, снятое со стека данных.Пример. Слово 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
)defvar ‹имя› ‹начальное значение›
,
при выполнении defvar
определяется слово ‹имя›
, которое кладёт на стек
текущее значение переменной.set ‹имя›
, слово set
снимает
со стека число и присваивает его переменной с заданным именем.Пример.
(interpret #(defvar counter 0
define nextnum
counter dup 1 + set counter
end
nextnum nextnum
nextnum nextnum +
nextnum nextnum *)
'()) → (20 5 1 0)