5.4.3 Blocos, Atribuições e Declarações
Blocos
O corpo de um bloco é avaliado com respeito ao ambiente atual estendido por um frame que vincula todos os nomes locais ao valor "*unassigned*". Temporariamente fazemos uso do registrador val para armazenar a lista de todas as variáveis declaradas no bloco, que é obtida por scan_out_declarations da seção 4.1.1. As funções scan_out_declarations e list_of_unassigned são assumidas como estando disponíveis como operações de máquina.1
"ev_block",
assign("comp", list(op("block_body"), reg("comp"))),
assign("val", list(op("scan_out_declarations"), reg("comp"))),
save("comp"), // para que possamos usá-lo temporariamente para valores *unassigned*
assign("comp", list(op("list_of_unassigned"), reg("val"))),
assign("env", list(op("extend_environment"),
reg("val"), reg("comp"), reg("env"))),
restore("comp"), // o corpo do bloco
go_to(label("eval_dispatch")),
Atribuições e declarações
Atribuições são tratadas por ev_assignment, que é alcançado a partir de eval_dispatch com a expressão de atribuição em comp. O código em ev_assignment primeiro avalia a parte do valor da expressão e então instala o novo valor no ambiente. A função assign_symbol_value é assumida como estando disponível como uma operação de máquina.
"ev_assignment",
assign("unev", list(op("assignment_symbol"), reg("comp"))),
save("unev"), // salva variável para depois
assign("comp", list(op("assignment_value_expression"), reg("comp"))),
save("env"),
save("continue"),
assign("continue", label("ev_assignment_install")),
go_to(label("eval_dispatch")), // avalia valor da atribuição
"ev_assignment_install",
restore("continue"),
restore("env"),
restore("unev"),
perform(list(op("assign_symbol_value"),
reg("unev"), reg("val"), reg("env"))),
go_to(reg("continue")),
Declarações de variáveis e constantes são tratadas de maneira similar. Note que enquanto o valor de uma atribuição é o valor que foi atribuído, o valor de uma declaração é undefined. Isso é tratado configurando val como undefined antes de continuar. Assim como no avaliador metacircular, transformamos uma declaração de função em uma declaração de constante cuja expressão de valor é uma expressão lambda. Isso acontece em ev_function_declaration, que faz a transformação no lugar em comp e passa para ev_declaration.
"ev_function_declaration",
assign("comp",
list(op("function_decl_to_constant_decl"), reg("comp"))),
"ev_declaration",
assign("unev", list(op("declaration_symbol"), reg("comp"))),
save("unev"), // salva nome declarado
assign("comp",
list(op("declaration_value_expression"), reg("comp"))),
save("env"),
save("continue"),
assign("continue", label("ev_declaration_assign")),
go_to(label("eval_dispatch")), // avalia valor da declaração
"ev_declaration_assign",
restore("continue"),
restore("env"),
restore("unev"),
perform(list(op("assign_symbol_value"),
reg("unev"), reg("val"), reg("env"))),
assign("val", constant(undefined)),
go_to(reg("continue")),
Exercício 5.25
Estenda o avaliador para tratar laços while, traduzindo-os para aplicações de uma função while_loop, conforme mostrado no exercício 4.7. Você pode colar a declaração da função while_loop na frente dos programas do usuário. Você pode "trapacear" assumindo que o transformador sintático while_to_application está disponível como uma operação de máquina. Consulte o exercício 4.7 para discutir se essa abordagem funciona se instruções return, break e continue são permitidas dentro do laço while. Se não, como você pode modificar o avaliador de controle explícito para executar programas com laços while que incluem essas instruções?
💡 Mostrar solução — tente primeiro!💡 Esconder solução
Com a "trapaça" permitida: um caso novo no despacho reconhece o laço e o transforma:
"ev_while",
assign("comp", list(op("while_to_application"), reg("comp"))),
go_to(label("ev_application")),
com a declaração de while_loop pré-colada nos programas do usuário (a função recursiva de cauda do exercício 4.7 — que o avaliador de controle explícito, sendo tail-recursive, itera em espaço constante).
E return/break/continue dentro do laço? A tradução por função quebra: um return dentro do corpo retornaria da função auxiliar do laço, não da função do usuário — e break/continue nem têm para onde ir. A discussão do exercício 4.7 se aplica: ou o corpo é traduzido em estilo de passagem de continuações (break e continue viram funções passadas ao corpo), ou — a rota natural AQUI — o avaliador de controle explícito implementa o laço diretamente no controlador, com rótulos: while_test → corpo → while_test, em que break é um go_to para o rótulo de saída, continue um go_to para o teste, e return continua fazendo o que sempre fez (reverter a pilha ao marcador da função). No nível de máquina de registradores, laços e saltos são o idioma nativo — a dificuldade da tradução funcional simplesmente desaparece.
Exercício 5.26
Modifique o avaliador de modo que ele use avaliação em ordem normal, baseado no avaliador preguiçoso da seção 4.2.
💡 Mostrar solução — tente primeiro!💡 Esconder solução
As mudanças espelham a seção 4.2 no nível do controlador:
- Aplicações: em vez de avaliar os argumentos (
ev_operand_loop), construir a lista de thunks — cada operando viralist("thunk", expressão, ambiente)sem passar pelo avaliador; só a expressão de função é forçada (actual_value). - Forçamento: um novo trecho
force_itno controlador: sevalé um thunk, empurra o estado, avalia a expressão do thunk no ambiente do thunk (reentrando emeval_dispatch) e, com memoização, sobrescreve o thunk com o valor. - Pontos estritos: onde o valor é usado — o teste do condicional (
ev_conditionalforçavalantes dobranch), aplicação de primitivas (força cada argumento antes deapply_primitive_function), e o driver ao imprimir.
Funções compostas recebem os thunks sem forçar — é exatamente o delay_it da seção 4.2 transcrito para registradores, com o detalhe novo de que "chamar o avaliador recursivamente para forçar" vira save/go_to eval_dispatch/restore explícitos no controlador.