Пока у вас есть хорошие ответы; позвольте мне привести вам непрактичный, но очень образовательный пример того, как вы могли бы создать язык без понятия стеков или «потока управления» вообще. Вот программа, которая определяет факториалы:
function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = f(3)
Мы помещаем эту программу в строку, и мы оцениваем программу по текстовой подстановке. Поэтому, когда мы оцениваем f(3)
, мы выполняем поиск и заменяем на 3 для i, например так:
function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = if 3 == 0 then 1 else 3 * f(3 - 1)
Отлично. Теперь мы выполняем другую текстовую подстановку: мы видим, что условие «if» ложно, и выполняем замену другой строки, создавая программу:
function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = 3 * f(3 - 1)
Теперь мы заменим еще одну строку для всех подвыражений, содержащих константы:
function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = 3 * f(2)
И вы видите, как это происходит; Я не буду вдаваться в подробности. Мы могли бы продолжать выполнять серию подстановок строк, пока мы не приступим к работе let x = 6
и не закончим.
Мы традиционно используем стек для локальных переменных и информации о продолжении; помните, что стек не говорит вам, откуда вы пришли, он говорит вам, куда вы идете дальше с этим возвращаемым значением в руке.
В модели программирования подстановки строк в стеке нет «локальных переменных»; формальные параметры заменяются на их значения, когда функция применяется к ее аргументу, а не помещается в таблицу поиска в стеке. И здесь нет «идти куда-то дальше», потому что оценка программы просто применяет простые правила для подстановки строк для создания другой, но эквивалентной программы.
Теперь, конечно, на самом деле выполнение подстановок строк - это, вероятно, не тот путь. Но языки программирования, которые поддерживают «эквациональные рассуждения» (такие как Haskell), логически используют эту технику.