4.4. Переходы различных типов |
||
К переходам традиционно относят операции J, Z, %. Что касается первой операции, то она состоит в выталкивании из стека одного значения, которое является адресом, по которому хранится значение метки. Дале по таблице меток, сгенирированной на этапе СА, определяется точка перехода. Далее, анализ продолжается не со следующего символа в ОПЗ, а с символа, на который указывает метка, то есть просто изменяется порядок анализа. Для команды Z выталкивается 2 значения из стека – адрес значения метки и адрес управляющего значения. Если управляющее значение равно нулю, то аналогично команде J отрабатывается переход по метке, в противном случае интерпретация продолжается без нарушения команды. Очевидно, что в этом случае нарушается линейность количества выполняемых операций в зависимости от длины ОПЗ. Трудоемкость будет определяться трудоемкостью алгоритма, реализованного в ОПЗ. Пример. WHILE i<=n DO { s:=s+i; i:=i+1 }; I3I1O~L2OZ (метка M1 – перед началом) I2I2I3O+O@ I3I3C3O+O@ L1OJ Операция % несколько сложнее. При вызове функции нужно помимо передачи управления по метке последовательно загрузить в стек вызовов (дополнительный стек) точку возврата, состояние программы и значение (я) параметра (ов). Под точкой возврата понимается адрес, по которому будет передано управление по команде ret, им является следующий элемент ОПЗ после операции %. Что понимать под состоянием программы зависит от конкретного языка и механизма хранения локальных переменных. Если в языке есть понятие области действия, то переменные с локальной областью действия (выявленной в КА) также хрянятся в стеке. В ассемблере вызов команды call подразумевает также сохранение в стеке значений системных регистров, чтобы управляющие команды внутри подпрограммы не влияли на управление основной программой. В рассматриваемом нами языке нет определений областей видимости, поэтому здесь каждая переменная может быть изменена внутри процедуры, и соответственно для обеспечения автономности подпрограммы придется сохранять значения всех переменных в стеке. По такому принципу работает, к примеру, интерпретатор скриптового языка php, однако там есть возможности исключить переменную из этого локального спиcка, применив описатель global. Значения параметров помещается в стек в последнюю очередь, что позволит их извлечь после передачи управления (команда G). Если параметров несколько, то важно четко соблюдать устанволенный порядок («справо налево» - С или «слева направо» - Pascal). Команда позволяет извлечь очередной параметр, который был запомнен в стеке. В некоторых языках, например в Паскале существует два способа передачи параметров – прямой и ссылочный (косвенный – var). Команда G позволяет получить очередное значение из стека. Соответственно исполнение команды состоит в извлечении значения из основного стека – адреса назначения (куда извлекаем), а из стека вызовов – значение параметра. Команда R обеспечивает корректный возврат. Логика этой команды должна соответствовать команде %. Так, если в стеке вызовов были запомнены какие-то значения, то они выполняется восстановление этих значений из стека по исходным адресам. Если в стеке вызовов сохраняются локальные переменные, то соответствующая область очищается. Кроме того, из стека извлекается метка возврата для передачи управления. Если тип подпрограммы- функция (в pascal-терминологии), то после выполнения команды в основном стеке останется результат, являющийся параметром команды R. На самом деле, можно обойтись одним стеком. Однако, если мы реализуем интерпретатор на типизированном языке, это не очень удобно, т.к. элементы основного стека – указатели, а элементы дополнительно стека – значения. В случае, если эти значения могут быть разных типов – реализация усложняется. |
||