Um tradutor é uma coleção de declarações de atribuições em D oferecidas pelo fornecedor de uma interface que pode ser usada para traduzir uma expressão de entrada em um objeto do tipo struct. Para entender a necessidade e o uso de tradutores, consideraremos como um exemplo as rotinas da biblioteca padrão ANSI-C definidas em stdio.h. Essas rotinas operam em estruturas de dados chamadas FILE, cujos artefatos de implementação são abstraídos dos programadores de C. Uma técnica padrão para criar uma abstração de estrutura de dados é fornecer somente uma declaração de reenvio de uma estrutura de dados em arquivos de cabeçalho públicos, enquanto mantém a definição de struct correspondente em um arquivo de cabeçalho privado separado.
Se você estiver escrevendo um programa em C e deseja saber o descritor do arquivo correspondente a uma struct FILE, use a função fileno(3C) para obter o descritor em vez de cancelar diretamente a referência a um membro da struct FILE . Os arquivo de cabeçalho do Solaris reforçam essa regra, definindo a struct FILE como uma marca de declaração de reenvio opaca para que ela não tenha a sua referência cancelada diretamente pelos programas em C que incluem <stdio.h>. Dentro da biblioteca libc.so.1, imagine que fileno() é implementado em C, da seguinte forma:
int fileno(FILE *fp) { struct file_impl *ip = (struct file_impl *)fp; return (ip->fd); }
Nossa fileno() hipotética usa um ponteiro FILE como um argumento e o intercala com um ponteiro para uma estrutura libc interna correspondente, struct file_impl, em seguida, retorna o valor do membro fd da estrutura da implementação. Por que o Solaris implementa interfaces dessa forma? Abstraindo os detalhes da implementação libc atual dos programas clientes, a Sun é capaz de manter um compromisso com uma compatibilidade binária forte enquanto continua a desenvolver e alterar os detalhes da implementação interna de libc. Em nosso exemplo, o membro fd poderia mudar de tamanho ou de posição na struct file_impl, mesmo em um patch, e os binários existentes que chamam fileno(3C) não seriam afetados por essa mudança porque eles não dependem desses artefatos.
Infelizmente, um software de observação como o DTrace tem a necessidade de analisar a implementação para fornecer resultados úteis, e não tem o luxo de chamar funções arbitrárias de C definidas nas bibliotecas ou no kernel do Solaris. Você poderia declarar uma cópia de struct file_impl em seu programa em D para instrumentar as rotinas declaradas em stdio.h, mas, então, o seu programa em D confiaria em artefatos de implementação Privada da biblioteca que talvez quebrem em uma versão futura micro ou secundária, ou mesmo em um patch. Idealmente, desejamos fornecer uma construção a ser usada em programas em D que seja vinculada à implementação da biblioteca e seja atualizada de acordo, mas que ainda forneça uma camada adicional de abstração associada a uma estabilidade maior.
Um novo tradutor é criado através da uma declaração no formato:
translator output-type < input-type input-identifier > { member-name = expression ; member-name = expression ; ... };
O tipo de saída nomeia uma struct que será o tipo de resultado da tradução. O tipo de entrada especifica o tipo da expressão de entrada, e é colocado entre colchetes angulares < > e seguido por um identificador de entrada que pode ser usado nas expressões do tradutor como um alias da expressão de entrada. O corpo do tradutor é colocado entre chaves { } e terminado com um ponto-e-vírgula (;), e consiste em uma lista de nomes de membro e identificadores correspondentes às expressões de tradução. Cada declaração de membro deve nomear um membro exclusivo do tipo de saída e deve ser ter atribuída uma expressão de um tipo compatível ao tipo do membro, de acordo com as regras do operador (=) da atribuição de D.
Por exemplo, poderíamos definir uma struct de informações estáveis sobre os arquivos stdio com base em algumas interfaces libc disponíveis:
struct file_info { int file_fd; /* file descriptor from fileno(3C) */ int file_eof; /* eof flag from feof(3C) */ };
Um tradutor de D hipotético de FILE para file_info poderia ser declarado em D da seguinte forma:
translator struct file_info < FILE *F > { file_fd = ((struct file_impl *)F)->fd; file_eof = ((struct file_impl *)F)->eof; };
Em nosso tradutor hipotético, a expressão de entrada é do tipo FILE * e tem atribuído o identificador de entrada F. O identificador F pode ser usado nas expressões do membro do tradutor como uma variável do tipo FILE * que só esteja visível no corpo da declaração do tradutor. Para determinar o valor do membro file_fd da saída, o tradutor realiza uma transmissão e cancela a referência semelhante à implementação hipotética de fileno(3C), mostrada acima. Uma tradução semelhante é realizada para obter o valor do indicador EOF.
A Sun fornece um conjunto de tradutores a serem usados com as interfaces do Solaris que você chama a partir dos seus programas em D, e promete manter esses tradutores de acordo com as regras de estabilidade da interface definidas anteriormente quando a implementação da interface correspondente for alterada. Aprenderemos sobre esses tradutores mais tarde neste capítulo, depois que aprendermos como chamar os tradutores a partir de D. O recurso do tradutor em si também é fornecido para ser usado pelos desenvolvedores do aplicativo e da biblioteca que desejam oferecer seus próprios tradutores para que os programadores de D possam usar a fim de observar o estado de seus pacotes de software.