Очевидный способ добавить управляющие структуры в язык, основанный на стеке, — это добавить «стек управления». Я опишу механизм Postscript, потому что знаю его, но Форт ведет себя аналогично (конечно, с небольшими отличиями).
Самый простой управляемый цикл — это цикл repeat
. Технически бесконечный loop
проще, но для его использования требуется явная команда exit
, и ее объяснение усложнит ситуацию.
Синтаксис повтора:
int proc **repeat** -
Поэтому, когда начинается команда repeat
, она ожидает найти процедуру на вершине стека операндов (это тело цикла, которое нужно выполнить) и целое число непосредственно под процедурой (количество раз для выполнения процедуры).
Поскольку также существует стек выполнения (я думаю, Форт называет его стеком «возврата»), команда может «выполнять» другие команды, помещая их в стек выполнения, таким образом планируя выполнение других команд сразу после завершения текущей команды. , но перед возобновлением вызова текущей команды. Это длинное предложение, но ключевая мысль там.
В качестве конкретного примера предположим, что интерпретатор выполняется из входного потока. И стеки выглядят так:
operand: -empty-
execute: <stdin>
Пользователь вводит 2 {(Hello World!\n) print} repeat
.
Целое число 2 помещается в стек.
operand: 2
execute: <stdin>
Тело процедуры в кавычках (*) помещается в стек.
operand: 2 {(Hello World!\n) print}
execute: <stdin>
Команда repeat
выполняется.
operand: 2 {(Hello World!\n) print}
execute: <stdin> repeat
Repeat: expects operands: int proc
if int<0 Error
if int==0 return //do nothing
push `repeat` itself on exec stack
push quoted proc on exec stack
push int-1 on exec stack
push "executable" proc on exec stack
Выполнение процедуры повтора (один раз) оставляет стеки такими:
operand: -empty-
execute: <stdin> repeat {(Hello World!\n) print} 1 **{(Hello World!\n) print}**
Интерпретатор выполняет процедуру поверх стека exec, печатая «Hello World!», затем находит целое число и помещает его в стек op.
operand: 1
execute: <stdin> repeat {(Hello World!\n) print}
Интерпретатор выполняет процедуру в кавычках, помещая ее в стек операций.
operand: 1 {(Hello World!\n) print}
execute: <stdin> repeat
И мы вернулись к началу! Готов к следующей итерации (или завершению, если целое число упало до нуля).
Надеюсь это поможет.
Изменить;
Посмотрев на ваш код, у меня есть другое предложение, возможно, трамплин к чему-то вроде того, что я описал выше. Я думаю, вам следует на время забыть о коде и сосредоточиться на структурах данных. У вас есть хорошая таблица для переменных, но все команды — это встроенные литералы, разбросанные по коду!
Если ваша таблица содержит переменные типы записей, вы можете использовать один и тот же механизм поиска для обоих (и даже перейти к хэшу или троичному дереву поиска (мой текущий фаворит)). Я имею в виду что-то вроде этого:
struct object {
int type;
union {
int i;
void (*command)(context *);
} u;
};
struct dict {
int sz;
struct rec {
char *key;
object val;
} data[]; //think resizable!
};
Таким образом, каждая команда выполняет свою функцию. Бонус: небольшие функции. Вы должны попытаться сделать свои функции достаточно маленькими, чтобы вы могли видеть все это на экране одновременно. Сканирование всего сразу позволяет вашему правому полушарию выполнять часть работы по обнаружению проблемных областей.
Тогда вам понадобится другой тип для хранения последовательностей команд. Массив или список должны работать. Когда вы можете определить последовательности, вам будет намного легче повторять последовательности.
Бонус в том, что вы всего в одной конструкции от Turing Complete. Последовательности, итерации, решения (или выборка): это все, что вам нужно для программирования любой вычисляемой функции!
*. Как обнаружил комментатор, Postscript на самом деле не имеет той же концепции цитирования, которую я использую здесь в своем описании. Здесь я заимствую эту концепцию из терминологии Лиспа. Вместо этого Postscript имеет флаг literal/executable, который можно установить cvx
cvlit
или проверить xcheck
. Будет выполнен исполняемый массив в стеке выполнения. Таким образом, тело процедуры "в кавычках" фактически является буквальным телом процедуры (т. е. массивом). Из-за этого repeat
также должен отправить вызов cvx
для выполнения перед следующей итерацией. Итак, более правильный псевдокод для постскриптумной реализации repeat
:
Repeat: expects operands: int proc
if int<0 Error
if int==0 return //do nothing
push `repeat` itself on exec stack
push 'cvx' on the exec stack
push cvlit(proc) on exec stack
push int-1 on exec stack
push "executable" proc on exec stack
При этом выполняется необходимая перестановка флагов для передачи процедуры как данных в стек выполнения.
Я видел еще один способ реализации этой управляющей структуры с двумя функциями, repeat
одной и той же точкой входа и внутренним оператором продолжения, который теоретически не нуждается в имени. Я думаю, что ghostscript называет это чем-то вроде @repeat-continue@. С отдельной функцией continue вам не нужно так тщательно следить за порядком элементов в стеке, и вам не нужно менять флаг literal. Вместо этого вы можете хранить некоторые постоянные данные ниже рекурсивного вызова в стеке exec; но его тоже надо убирать.
Таким образом, альтернативным repeat
будет:
int proc **repeat** -
if int<0 Error
if int==0 return //do nothing
push null on exec stack <--- this marks our "frame"
push int-1 on exec stack
push proc on exec stack
push '@repeat-continue' on exec stack
push executable proc on exec stack
Продолжение также имеет более простую работу.
@repeat-continue
peek proc from exec stack
peek int from exec stack
if int==0 clear-to-null and return
push '@repeat-continue' on exec stack
push executable proc on exec stack
Наконец, оператор exit
тесно связан с циклами, он очищает стек exec до «кадра» оператора цикла. Для стиля с двумя функциями это объект null
или аналогичный. Для стиля с 1 функцией это сам оператор цикла.
person
luser droog
schedule
05.08.2011