A resposta intuitiva é que, se você não tem loops ilimitados e não tem recursão e não tem que ir, seus programas terminam. Isso não é bem verdade, existem outras maneiras de se infiltrar na não terminação, mas é bom o suficiente para a maioria dos casos práticos. É claro que o inverso está errado, existem linguagens com essas construções que não permitem programas que não terminam, mas usam outros tipos de restrições, como sistemas de tipos sofisticados.
Recursão
Uma restrição comum nas linguagens de script é impedir dinamicamente a recursão: se A chama B chama C chama ... chama A, o intérprete (ou verificador, no seu caso) desiste ou sinaliza um erro, mesmo que a recursão possa realmente terminar. Dois exemplos concretos:
O pré-processador C deixa uma macro intacta enquanto a expande. O uso mais comum é definir um wrapper em torno de uma função:
#define f(x) (printf("calling f(%d)\n", (x)), f(x))
f(3);
Isso se expande para
(printf("calling f(%d)\n", (3)), f(3))
A recursão mútua também é tratada. Uma conseqüência é que o pré-processador C sempre termina, embora seja possível criar macros com alta complexidade de tempo de execução.
#define f0(x) x(x)x(x)
#define f1(x) f0(f0(x))
#define f2(x) f1(f1(x))
#define f3(x) f2(f2(x))
f3(x)
Os shells Unix expandem aliases recursivamente, mas apenas até encontrarem um alias que já está sendo expandido. Novamente, o objetivo principal é definir um alias para um comando com nome semelhante.
alias ls='ls --color'
alias ll='ls -l'
Uma generalização óbvia é permitir uma profundidade de recursão de até , com talvez sendo configurável.nn
Existem técnicas mais gerais para provar que as chamadas recursivas terminam, como encontrar um número inteiro positivo que sempre diminui de uma chamada recursiva para a seguinte, mas essas são consideravelmente mais difíceis de detectar. Eles geralmente são difíceis de verificar e muito menos inferir.
rotações
Os loops são encerrados se você pode vincular o número de iterações. O critério mais comum é que, se você tiver um for
loop (sem truques, isto é, aquele que realmente conta de a ), ele executa um número finito de iterações. Portanto, se o corpo do loop terminar, o próprio loop será finalizado.mn
Em particular, com loops for (além de construções de linguagem razoáveis, como condicionais), você pode escrever todas as funções recursivas primitivas e vice-versa. Você pode reconhecer funções recursivas primitivas sintaticamente (se elas forem escritas de maneira não ofuscada), porque elas não usam loop while ou goto ou recursão ou outro truque. É garantido que as funções recursivas primitivas terminam e a maioria das tarefas práticas não vai além da recursão primitiva.