Verity 0.10, otimizado para o tamanho do código-fonte (1944 bytes)
Eu originalmente interpretei mal a questão e a interpretei como um código-golfe. Provavelmente foi o melhor, pois é muito mais fácil escrever um quine com código-fonte curto do que com código de objeto curto, de acordo com as restrições da pergunta; isso tornou a pergunta fácil o suficiente para que eu pudesse produzir uma resposta razoável e pudesse funcionar como um trampolim no caminho para uma resposta melhor. Também me levou a usar uma linguagem de nível superior para a entrada, o que significa que eu precisaria expressar menos no próprio programa. Não criei o Verity como uma linguagem de golfe para hardware (na verdade, fui contratado para criá-lo há algum tempo em um contexto completamente diferente), mas há uma reminiscência lá (por exemplo, é um nível substancialmente mais alto do que um HDL típico, e tem muito menos clichê; também é muito mais portátil que o HDL típico).
Tenho certeza de que a solução correta para o código de objeto curto envolve armazenar os dados em algum tipo de estrutura em árvore, já que a pergunta não permite o uso do bloco ROM, que é onde você normalmente os armazena em um programa prático; Talvez eu escreva um programa que use esse princípio (não sei qual idioma, talvez Verity, talvez Verilog; o VHDL tem muitos clichês para provavelmente ser o ideal para esse tipo de problema) em algum momento. Isso significaria que você não precisaria passar todos os bits do código fonte para todos os bits da sua "ROM criada manualmente". No entanto, o compilador Verity atualmente sintetiza a estrutura da saída com base na precedência e associatividade da entrada, o que significa que está efetivamente representando o ponteiro da instrução (portanto, o índice da tabela de pesquisa) em unário,
O próprio programa:
import <print>new x:=0$1296in(\p.\z.\a.new y:=(-a 5-a 1-a 1-a 2-a 4-a 2-a 3-a 2-a 6-a 2-a 0-a 3-a 0-a 4-a 4-a 7-a 4-a 2-a 6-a 2-a 5-a 1-a 2-a 2-a 0-a 3-a 6-a 7-a 2-a 2-a 1-a 1-a 3-a 3-a 0-a 4-a 4-a 3-a 2-a 7-a 5-a 7-a 0-a 6-a 4-a 4-a 1-a 6-a 2-a 6-a 1-a 7-a 6-a 6-a 5-a 1-a 2-a 2-a 0-a 5-a 0-a 0-a 4-a 2-a 6-a 5-a 0-a 0-a 6-a 3-a 6-a 5-a 0-a 0-a 5-a 0-a 6-a 5-a 2-a 2-a 1-a 1-a 3-a 3-a 0-a 4-a 5-a 3-a 2-a 7-a 5-a 7-a 0-a 5-a 5-a 5-a 1-a 4-a 4-a 3-a 1-a 5-a 5-a 1-a 2-a 2-a 0-a 4-a 3-a 3-a 4-a 1-a 5-a 1-a 0-a 2-a 1-a 1-a 1-a 4-a 4-a 3-a 6-a 7-a 0-a 6-a 0-a 1-a 3-a 2-a 0-a 5-a 4-a 2-a 0-a 5-a 5-a 1-a 2-a 1-a 0-a 4-a 6-a 3-a 4-a 7-a 3-a 6-a 2-a 6-a 0-a 3-a 4-a 1-a 1-a 1-a 2-a 2-a 0-a 4-a 6-a 3-a 3-a 5-a 1-a 7-a 2-a 6-a 1-a 1-a 0-a 2-a 7-a 2-a 1-a 1-a 0-a 4-a 6-a 3-a 1-a 5-a 3-a 7-a 5-a 1-a 2-a 1-a 0-a 4-a 6-a 3-a 5-a 7-a 5-a 7-a 4-a 6-a 5-a 6-a 0-a 3-a 4-a 1-a 1-a 1-a 2-a 2-a 0-a 4-a 3-a 3-a 4-a 1-a 5-a 1-a 0-a 2-a 1-a 1-a 1-a 4-a 5-a 3-a 6-a 7-a 0-a 6-a 0-a 1-a 3-a 2-a 0-a 5-a 4-a 2-a 0-a 4-a 1-a 7-a 7-a 6-a 3-a 7-a 4-a 2-a 0-a 4-a 3-a 6-a 2-a 6-a 3-a 7-a 4-a 2-a 0-a 5-a 4-a 6-a 0-a 7-a 2-a 0-a 1-a 4-a 5-a 3-a 4-a 4-a 4-a 4-a 3-a 6-a 4-a 4-a 4-a 4-a 3-a 6-a 2-a 6-a 1-a 5-a 3-a 7-a 4-a 2-a 0-a 4-a 4-a 6-a 5-a 6-a 3-a 7-a 5-a 3-a 2-a 7-a 5-a 7-a 1-a 4-a 5-a 3-a 6-a 7-a 6-a 7-a 3-a 6-a 1-a 5-a 1-a 1-a 0-a 2-a 7-a 2-a 1-a 1-a 0-a 4-a 7-a 2-a 7-a 1-a 5-a 1-a 4-a 2-a 3-a 7-a 4-a 3-a 2-a 7-a 5-a 7-a 1-a 4-a 4-a 3-a 6-a 7-a 6-a 7-a 6-a 6-a 1-a 5-a 1-a 5-a 4-a 2-a 6-a 2-a 5-a 1-a 2-a 2-a 0-a 3-a 0-a 5-a 1-a 4-a 4-a 3-a 4-a 4-a 4-a 4-a 6-a 6-a 4-a 4-a 4-a 4-a 3-a 6-a 2-a 6-a 1-a 5-a 0-a 5-a 0-a 0-a 0-a 1-a 6-a 5-a 4-a 3-a 2-a 7-a 5-a 7-a 1-a 4-a 4-a 3-a 6-a 7-a 6-a 7-a 3-a 6-a 2-a 0-a 0-a 1-a 4-a 7-a 4-a 7-a 1-a 6-a 2-a 6-a 1-a 7-a 3-a 6-a 3-a 7-a 0-a 6-a 1-a 5-!x)in while!x>0do(p(if z<32then z+92else z);if z==45then while!y>0do(p 97;p 32;p(48^!y$$3$$32);p 45;y:=!y>>3)else skip;x:=!x>>6))print(!x$$6$$32)(\d.x:=!x>>3^d<<1293;0)
Mais legível:
import <print>
new x := 0$1296 in
(\p.\z.\a.
new y := (-a 5-a 1-
# a ton of calls to a() omitted...
-a 1-a 5-!x) in
while !x>0 do (
p(if z<32 then z+92 else z);
if z==45
then while !y>0 do (
p 97;
p 32;
p(48^!y$$3$$32);
p 45;
y:=!y>>3 )
else skip;
x:=!x>>6
)
)(print)(!x$$6$$32)(\d.x:=!x>>3^d<<1293;0)
A ideia básica é que armazenemos todos os dados na variável x
. (Como é habitual em um quine, temos uma seção de código e uma seção de dados; os dados codificam o texto do código e também podem ser usados para regenerar o texto dos dados.) Infelizmente, atualmente, o Verity atualmente não permite muito grande constantes a serem escritas no código-fonte (ele usa inteiros OCaml durante a compilação para representar números inteiros na fonte, o que claramente não está correto em uma linguagem que suporta tipos inteiros arbitrariamente amplos) - além disso, não permite que constantes sejam especificado em octal - geramos o valor x
em tempo de execução por meio de chamadas repetidas para uma funçãoa
. Poderíamos criar uma função nula e chamá-la repetidamente como instruções separadas, mas isso tornaria difícil identificar por onde começar a produzir o texto da seção de dados. Então, em vez disso, a
retornei um número inteiro e utilizei a aritmética para armazenar os dados (o Verity garante que a aritmética é avaliada da esquerda para a direita). A seção de dados é codificada x
usando um único -
sinal; quando isso é encontrado no tempo de execução, é expandido ao máximo -a 5-a 1-
etc., por meio do uso de y
.
Inicializar y
como uma cópia de x
é bastante sutil aqui. Como a
retorna zero especificamente, a maior parte da soma é apenas zero menos zero menos ... e se cancela. Terminamos com !x
(ou seja, "o valor de x
"; no Verity, como no OCaml, o nome de uma variável funciona mais como um ponteiro do que qualquer outra coisa, e você deve desreferenciá-lo explicitamente para obter o valor da variável). As regras da Verity para menos unário são um pouco complexas - o menos unário de v
é escrito como (-v)
- assim (-0-0-0-!x)
analisa como (-(0-0-0-!x))
, que é igual a !x
, e acabamos inicializando y
como uma cópia de x
. (Também vale a pena notar que o Verity não échamar por valor, mas permite que funções e operadores escolham a ordem em que avaliam as coisas; -
avaliará o argumento esquerdo antes do argumento direito e, em particular, se o argumento esquerdo tiver efeitos colaterais, eles serão visíveis quando o argumento direito for avaliado.)
Cada caractere do código fonte é representado usando dois dígitos octais. Isso significa que o código-fonte é limitado a 64 caracteres diferentes, então tive que criar minha própria página de código para uso interno. A saída é em ASCII, então eu precisei converter internamente; é para isso que (if z<32 then z+92 else z)
serve. Aqui está o conjunto de caracteres que usei na representação interna, em ordem numérica (ou seja, \
tem o ponto de código 0, o ?
ponto de código 63):
\]^_`abcdefghijklmnopqrstuvwxyz{ !"#$%&'()*+,-./0123456789:;<=>?
Esse conjunto de caracteres fornece a maioria dos caracteres importantes para o Verity. Caracteres notáveis ausentes são }
(o que significa que não podemos criar um bloco usando {}
, mas felizmente todas as instruções são expressões para que possamos usá-lo ()
); e |
(é por isso que eu tive que usar um OR exclusivo e não inclusivo ao criar o valor de x
, o que significa que eu preciso inicializá-lo para 0; no entanto, eu preciso especificar o tamanho dele). Alguns dos caracteres críticos que eu queria garantir estavam no conjunto de caracteres <>
(para importação e também para turnos), ()
(muito difícil escrever um programa que possa ser analisado sem eles), $
(para tudo relacionado à largura de bit) e \
( para lambdas; teoricamente, poderíamos contornar isso comlet…in
mas seria muito mais detalhado).
Para tornar o programa um pouco mais curto, construí abreviações para print
e para !x$$6$$32
(ou seja, "os 6 bits inferiores !x
, convertidos para serem utilizados na print
biblioteca), ligando-os temporariamente aos argumentos lambda.
Finalmente, há a questão da saída. O Verity fornece uma print
biblioteca destinada à saída de depuração. Em um simulador, ele imprime os códigos ASCII na saída padrão, o que é perfeitamente utilizável para testar o programa. Em uma placa de circuito físico, depende de uma print
biblioteca ter sido escrita para o chip e a placa em torno dele; há uma print
biblioteca na distribuição Verity para um painel de avaliação ao qual tive acesso que imprime a saída em displays de sete segmentos. Dado que a biblioteca acabará ocupando espaço na placa de circuito resultante, pode valer a pena usar um idioma diferente para uma solução otimizada para esse problema, para que possamos enviar os bits da saída diretamente nos fios.
A propósito, este programa é O (n²) no hardware, o que significa que é muito pior em um simulador (suspeito de O (n⁴); porém, não tenho certeza, mas era difícil o suficiente para simular que parece improvável que seja cúbico e com base em como o tempo reagiu às minhas alterações enquanto eu escrevia o programa, a função parece crescer muito rapidamente). O compilador Verity precisava de 436 passes de otimização (o que é muito, muito mais do que normalmente usaria) para otimizar o programa e, mesmo depois disso, simulá-lo era muito difícil para o meu laptop. A execução completa de compilação e simulação levou o seguinte tempo:
real 112m6.096s
user 105m25.136s
sys 0m14.080s
e atingiu o máximo de 2740232 kibibytes de memória. O programa leva um total de 213646 ciclos de relógio para executar. Mas funciona!
Enfim, essa resposta realmente não atende à pergunta, pois eu estava otimizando a coisa errada, mas, como ainda não há outras respostas, essa é a melhor por padrão (e é bom ver como seria uma coluna de golfe em uma linguagem de hardware). No momento, não tenho certeza se trabalharei ou não em um programa que visa produzir uma saída mais otimizada no chip. (Provavelmente seria muito maior em termos de origem, pois uma codificação de dados O (n) seria bastante mais complexa do que a vista aqui.)