SmallForth: an implementation over Pharo Smalltalk – Part 10

Voltar ao índice.

Post anterior.

As palavras Forth neste post podem ser vistas em 6. Throw It For a Loop.

Loop definido

DO … LOOP

A palavra DO armazena as variáveis limit e index. E armazena também a posição na stream que contém as palavras logo após o DO. Esta posição será usada na palavra LOOP (na verdade no método repeat) para fazer o interpretador voltar ao início e repetir a execução a depender do valor de index.

Loop com contador

Vamos implementar a palavra ‘I’ que coloca o index do loop no topo da pilha.

Calculando juros compostos

Já temos palavras suficientes para executar o exemplo abaixo, retirado da página 6. Throw It For a Loop.

: COMPOUND  ( amount interest -- )           
CR SWAP 21 1 DO  ." YEAR " I . 3 SPACES                            
2DUP R% +  DUP ." BALANCE " . CR                      
LOOP  2DROP ;

: R%  10 */  5 +  10 / ;

1000 6 COMPOUND

Usamos >R e >R para guardar na pilha de retorno o parâmetro que representa o número de repetições do cálculo que antes era fixo em 20 (anos).

: COMPOUND  ( amount interest years -- )           
>R
CR SWAP R> 1+ 1 DO  ." YEAR " I . 3 SPACES                            
2DUP R% +  DUP ." BALANCE " . CR                      
LOOP  2DROP ;

: R%  10 */  5 +  10 / ;

1000 6 25 COMPOUND

Loop com IF

Um if foi introduzido no loop abaixo para duplicar o “3” na pilha.

Loops aninhados

Testamos abaixo dois loops aninhados com dois contadores i e j.

Na implementação usamos a pilha de retorno.

A palavra do remove da pilha index e limit e coloca tudo na pilha de retorno junto com a posição na stream para poder iterar.

A palavra loop obtém index e limit da pilha de retorno. Incrementa index e se index já atingiu o valor de limit remove position da pilha de retorno e retorna ao loop de eval.

Caso contrário retorna a posição da stream ao início do loop e restaura index e limit na pilha de retorno.

A palavra I coloca o contador do loop (index) mais interno na pilha de parâmetros.

A palavra J coloca o contador do loop (index) mais externo na pilha de parâmetros.

Nota: Devido a um reset do meu computador tive que repor as credenciais SSH com o comando ssh-add ~/.ssh/id_rsa para poder fazer um push para o Github.

+LOOP

O incremento é obtido da pilha de parâmetros como um idioma similar ao seguinte: 3 +LOOP.

O loop com incremento.

Abaixo mostramos um exemplo de um +LOOP com incrementos variáveis.

CUIDADO! O código '20 0 do i dup +loop' é um loop infinito pois o incremento é sempre igual a ZERO.

Corrigindo +LOOP com incremento negativo

Havia um bug para loops com incremento negativo que foi sanado com a modificação abaixo.

Como limpar a pilha

Supondo que a palavra XX não existe ela deve causar um erro e, ao mesmo tempo, esvaziar a pilha.

No método abaixo, que procura a última versão de uma palavra, ao lançar o erro missing word também passa a esvaziar a pilha.

Loops indefinidos

BEGIN … UNTIL

Nas palavras BEGIN e UNTIL usamos a pilha de retorno.

Em BEGIN armazenamos a posição na stream na pilha de retorno.

Em UNTIL obtemos o valor booleano da pilha de parâmetros. Se for false iteramos voltando ao início do loop. Se não, esvaziamos a pilha de retorno, prosseguindo para a próxima palavra depois do UNTIL.

Aninhando BEGIN UNTIL’s

A duas figuras abaixo mostram dois códigos equivalentes. O primeiro no SmallForth. O outro no Swift Forth.

Loops infinitos BEGIN … AGAIN

Limitando as iterações do loop ‘begin … again’

Mesmo loops que não se quer infinitos podem, por erro, iterar para sempre. Para evitar um acidente que possa travar o computador podemos estabelecer um limite numa fase de teste. Abaixo limitamos em 50 iterações.

A implementação do loop BEGIN … AGAIN já incorporou uma limitação configurável para o número máximo de iterações dos loops.

Limitando as iterações do loop DO … LOOP

É possível fazer com que um loop do tipo DO … LOOP se torne infinito. Normalmente por causa de um erro de programação como o que simulamos/forçamos abaixo interferindo nos controles do loop que ficam na pilha de retorno. Por isso o controle do número máximo foi também incorporado.

Abandonando o loop com LEAVE

A palavra LEAVE combinada com um IF permite encerrar um loop.

Vários tipos de loop podem ser encerrados por LEAVE no SmallForth.

Vamos inicialmente implementar no DO … LOOP (Que também funciona no Swift Forth).

A seguir vamos implementar adaptar LEAVE para também funcionar para o loop BEGIN … AGAIN (Embora não funcione no Swift Forth).

Incluimos AGAIN em skipUntilEndOfLoop.

Vamos permitir que se escape também com LEAVE do loop DO … +LOOP.

Limitando as iterações do loop DO … +LOOP

O novo teste criado para DO … +LOOP é similar ao que foi usado para DO … LOOP. E passa sem que seja preciso modificar o código nem da palavra DO e do método sinônimo para +LOOP.

Abaixo mostramos os métodos que continuam os mesmos.

Loops BEGIN … WHILE … REPEAT

Será criado um teste mais simples para este loop. Um teste mais amplo será feito quando tivermos a implementação de variáveis.

BEGIN não precisou ser alterado.

Este loop não precisa da palavra LEAVE para ser encerrado pois isto pode ser feito com WHILE.

Implementando variáveis

Vamos adiantar a implementação de variáveis no Forth (Ver 8. Variables, Constants, and Arrays).

Nossa implementação ainda é preliminar e voltaremos a ela para refiná-la (para uma maior integração com as palavras do usuário). Implementamos as variáveis para que sejam locais à palavra onde são definidas e para fazer sombra às variáveis definidas antes. Pensamos em fazer o mesmo com as definições de palavras no interior de outras. Não investigamos ainda se este é o comportamento padrão do Forth mas achamos que é um comportamento mais interessante para evitar coisas muito globais de difícil controle.

VARIABLE

Os dois métodos abaixo usam o truque de criar uma palavra fictícia _pushAddressOfVariable que é executada quando um nome de variável aparece como se fosse uma palavra. O efeito é colocar o endereço da variável na pilha de parâmetros.

!

@

Os teste abaixo mostra a variável date sendo definida também localmente no interior da palavra do usuário test. Note que o valor externo de date fica na sombra quando a execução está no interior da palavra test.

O teste seguinte é parecido com o anterior só envolvendo mais variáveis.

Revisitando o Loop BEGIN … WHILE … REPEAT

Agora podemos usar variáveis para testar o loop

A palavra repeat ainda não tinha sido arrolada em primitiveWords porque no teste simplificado anterior ela não tinha sido invocada.

Loops BEGIN … WHILE … REPEAT aninhados

O teste abaixo e vários outros foram gerados programaticamente.

A variável loopLevel controlará o nível de aninhamento.

O método initialize de ForthInterpreter estabelece o valor inicial de loopLevel.

Ao se executar BEGIN loopLevel é incrementada.

REPEAT não se alterou.

O método skipToAfterRepeat: abaixo substituiu o skipUntilRepeat que foi renomeado e ganhou um parâmetro para levar em conta o nível de aninhamento. Fizemos algo semelhante para controlar o aninhamento dos IF ELSE THEN.

A medida que o método skipToAfterRepeat vai encontrando as palavras BEGIN e REPEAT vai atualizando loopLevel para poder determinar qual o REPEAT que está no mesmo nível que o WHILE (e o BEGIN que o precedeu) para poder escapar do loop.

Os comandos usados no teste foram adaptados para execução no gforth online para comparar os resultados. Tivemos que criar a palavra STACK para mostrar todo o parameter stack porque a palavra .S estava truncando o resultado.

Limitando as iterações do loop BEGIN … WHILE … REPEAT

A palavra BEGIN não foi modificada no que funcionava quando implementamos a limitação do número de iterações para o par BEGIN AGAIN. Mas agora o controle da posição na stream está na pilha positionStack e não mais no return stack. O controle do limite máximo agora também está numa pilha própria: loopCountStack. Com isso os loops aninhados funcionam com dados próprios que são desempilhados quando o loop é encerrado.

Revendo as palavras J e criando a palavra I’

A palavra J precisa ser revista pois reduzimos o uso do return stack. Antes estavamos usando o mesmo para implementar o aninhamento dos loops. Agora separamos as pilhas usadas. Uma para a posição e outra para o limite de iteração.

A palavra I’ coloca na pilha de parâmetros o limite do loop corrente.

A palavra J coloca na pilha de parâmetros o contador do loop envolvente num aninhamento de loops.

Implementando I, J e I’ nos outros loops que não DO LOOP e DO +LOOP

As variáveis I, J e I’ foram estendidas aos loops que não DO LOOP ou DO +LOOP. Elas aparentemente não funcionam no Swift Forth ou no GForth.

O teste abaixo introduz as variáveis I, J e I’ no loop BEGIN AGAIN.

A palavra BEGIN estabelece que no primeiro loop I (index) valha 1 e I’ (limit) valha 0 (zero). limit não faz sentido para os loops indefinidos (não-limitados).

A palavra LEAVE também foi modificada para desempilhar index e limit.

Ao método abaixo, acionado pela palavra LEAVE, foi acrescentado o código para atualizar a pilha de pareamento dos IF ELSE THEN.

Repetimos abaixo, por conveniência, o método updatePairingIfElseThenStack.

A palavra AGAIN agora precisa incrementar a variável I.

No teste abaixo o uso das variáveis I e J é exercitado para teste de término do loop e são colocadas na pilha de parâmetros.

Note que I e J são variáveis relativas: I se refere ao loop corrente e J ao loop envolvente.

WHILE deve remover as variáveis I e I’ da pilha de retorno quando o loop terminar, o que acontece quando skipToAfterRepeat é invocado.

REPEAT faz o mesmo papel que AGAIN no loop BEGIN AGAIN de incrementar a variável I.

I e J também para o loop BEGIN UNTIL.

A palavra UNTIL tanto incrementa quanto esvazia a pilha de retorno.

Aninhando loops diferentes

Foram criados testes para todos os 12 arranjos 2 a 2 dos vários tipos de loops.

BEGIN (BEGIN AGAIN) UNTIL

BEGIN WHILE (BEGIN AGAIN) REPEAT

BEGIN WHILE (BEGIN UNTIL) REPEAT

BEGIN (BEGIN WHILE REPEAT) AGAIN

BEGIN (BEGIN WHILE REPEAT) UNTIL

BEGIN (DO LOOP) AGAIN

BEGIN (DO LOOP) UNTIL

BEGIN (BEGIN UNTIL) AGAIN

BEGIN WHILE (DO LOOP) REPEAT

DO (BEGIN AGAIN) LOOP

DO (BEGIN UNTIL) LOOP

DO (BEGIN WHILE REPEAT) LOOP

Palavras envolvidas na lógica do aninhamento

AGAIN foi reorganizada para levar em conta que agora a posição na stream é guardada em um pilha própria.

BEGIN não precisou ser modificada para que os loops heterogêneos aninhados funcionassem.

LEAVE foi modificada para consertar um bug. Agora LEAVE remove a posição de iteração do loop da pilha que armazena as posições na stream.

REPEAT não precisou ser modificada para que os loops heterogêneos aninhados funcionassem.

UNTIL não precisou ser modificada para que os loops heterogêneos aninhados funcionassem.

WHILE não precisou ser modificada para que os loops heterogêneos aninhados funcionassem.

PAGE e QUIT

A palavra PAGE limpa o Transcript no estágio atual da nossa implementação.

Nota: A palavra QUIT não será implementada ainda pois só faz sentido num REPL (O REPL do Forth imprime "Ok" após o pressionarmos a tecla ENTER/RETURN e QUIT evita isto).
Lembramos que o link para o file out atualizado até a parte 7 se encontra no primeiro post. 

A partir da parte 8 você pode encontrar SmallForth no GitHub em https://github.com/chicoary/small-forth.

Voltar ao índice.

Próximo post.

Um comentário

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s