Cognition: uma nova linguagem antisyntax que redefine a metaprogramação
(ret2pop.nullring.xyz)Introdução à linguagem de programação Cognition
Problema levantado
- Programadores de Lisp afirmam que é possível criar metaprogramação e sistemas generalizados com código S-expression e um sistema de macros funcional
- Mas o Lisp tem um problema fundamental
- A metaprogramação e a programação não são a mesma coisa, então o Lisp sempre tem uma sintaxe estrita (parênteses ou caracteres específicos para look-ahead)
- O parêntese esquerdo indica ao Lisp que deve continuar lendo até encontrar o parêntese direito
- Isso torna os parênteses esquerdo e direito imutáveis dentro da linguagem (conceitualmente não, mas impossível em algumas implementações)
- Mais importante, alterar depois a ordem em que esses tokens são distinguidos é impossível sem processamento de string
- Outras linguagens também têm maneiras diferentes de decidir, a partir de certos tokens, qual conteúdo será lido em seguida
- Esse processo é chamado de sintaxe
- O Cognition usa uma anti-sintaxe (antisyntax) que emprega notação pós-fixada completa
- Isso se parece com uma linguagem concatenativa, mas linguagens concatenativas também têm dois grandes problemas
- introdução de caracteres de colchete esquerdo/direito (na prática, notação prefixada)
- caractere de aspas para strings
- Isso também as torna inadequadas para uma linguagem de uso geral
- O mesmo problema é encontrado em implementações da sintaxe C do Lisp (abuso de caracteres de escape, necessidade de caracteres de espaço para distinguir início e fim de tokens específicos)
- Isso se parece com uma linguagem concatenativa, mas linguagens concatenativas também têm dois grandes problemas
- Racket possui um sistema de macros, mas não é dinâmico em runtime e usa pré-processamento
Introdução ao Cognition
- Um projeto que estive estudando com Matthew Hinton por alguns meses
- O objetivo é criar um dos sistemas de sintaxe mais generalizados conhecidos usando notação pós-fixada completa
- Pode ser necessário conhecimento prévio de sintaxe/tokenização/parse, mas tento explicar de forma que fique fácil de entender
- Repositório: https://github.com/metacrank/cognition
Baremetal Cognition
- O Baremetal Cognition é parecido com Brainfuck, mas com meta programação séria
- Exemplo de código de bootstrap:
\nldfgldftgldfdtgldf dfiff1 crank f\n - Espaços e quebras de linha são importantes
- Na 2ª linha há um espaço depois de
df - Na 3ª linha há um caractere de espaço
- Na 2ª linha há um espaço depois de
- Isso permite introduzir dois conceitos novos: delimitador (delimiter) e ignorar (ignore)
Tokenization
- Delimiter separa o início e o fim dos tokens para o tokenizer
- A lista de tokenizer de um único caractere é pública e pode ser lida e alterada dentro do Cognition
- Caracteres ignorados (Ignored character) são completamente ignorados pelo tokenizer na primeira etapa de todo loop read-eval-print
- ou seja, ao iniciar a coleta de tokens, ele pula o conjunto de caracteres ignorados configurado
- Por padrão, todos os caracteres são delimitadores e não há caracteres ignorados
- Com as listas de delimitadores e de ignorados, é possível alternar blacklist/whitelist dos caracteres dados (oferecendo concisão e praticidade)
Falias
- Falias é uma lista de palavras que é executada quando colocada na pilha
- Todo Falias executa o topo da pilha (equivalente ao
evalno Stem) fé o Falias padrão, executa o topo da pilhadsem ser colocado na pilhadaltera a lista de delimitadores para o valor de string da palavra (ou seja, exclui apenas o caractereldos delimitadores)- No ambiente padrão, nenhuma palavra é executada, exceto os Falias especiais
Observações sobre delimitadores
- Há uma regra interessante para delimitadores
- Se um caractere não for ignorado no loop de tokenização, o delimitador é incluído como parte do token atual e a coleta continua
- Isso contrasta com singlet (inclui a si mesmo e pula para encerrar a coleta do token)
- Também é possível configurar blacklist/whitelist
- Delimitadores, singlets e caracteres ignorados podem ser colocados em blacklist/whitelist
- Por padrão não há delimitadores em blacklist, singlets em whitelist nem caracteres ignorados em whitelist
- Todos os demais caracteres são coletados como parte do token atual, e novos caracteres continuam a entrar até que o loop seja interrompido pelas regras de delimitador ou singlet
Continuação do código de bootstrap
\nldf\n
- Faz com que
ldeixe de ser delimitador
gldftgldfdtgldf dfiff1 crank f
- Como
dé delimitador,glé colocado na pilha e o Faliasfé chamado, fazendoglnão ser delimitador tglé colocado na pilha edffaz com quetgdeixe de ser delimitadordtglé colocado na pilha e\ndffaz com que o\nseja o único caractere não-delimitador (a quebra de linha realmente está incluída no código)- Pela regra de delimitador, caracteres de espaço e
\nsão colocados na pilha (a 3ª linha inclui um espaço) - Outra palavra
\ \né tokenizada - A pilha atual é a seguinte (da base para o topo):
3. dtgl
2. [caractere de espaço] \n
- [caractere de espaço] \n
dfdefine\ \ncomo não-delimitadorifdefine\ \ncomo ignorado (ignorados no início da tokenização)fexecutadtgle armazena o toggledflagque alterna whitelist/blacklist de delimitadores- Agora todo caractere não-delimitador vira delimitador, e todo delimitador vira não-delimitador
- Finalmente, espaço e quebra de linha tornam-se delimitadores de token e são ignorados no início do token
- Em seguida,
1é tokenizado e colocado na pilha; em seguida, a palavracranké tokenizada e executada porf(1é tratado como número nesse caso, mas no Cognition tudo é palavra) - Sequência de bootstrap concluída! O que o
crankfaz é explicado na próxima seção
Resumo do bootstrap
- O Cognition permite alterar a tokenização programaticamente e de forma dinâmica
- algo impossível em outras linguagens
- é possível programar um tokenizer para uma linguagem-alvo dentro do Cognition e tokenizar como desejado
- Isso é possível por usar notação pós-fixada e não fazer look-ahead
- não é necessário analisar sintaticamente um ou mais tokens antes de avaliar uma expressão
- Com Falias, palavras podem ser executadas sem uso de palavras prefixadas ou execução de palavras padrão
Crank
- O sistema metacrank torna configurável a forma padrão de executar tokens colocados na pilha
- A palavra
crankrecebe um número e executa o topo da pilha toda vez que n palavras são colocadas na pilha - Código de exemplo (ambiente configurado com
crank 1):
5 crank 2
crank 2 crank
1 crank unglue swap quote prepose def
- No ambiente
crank 1, é possível suspender o uso defdurante a avaliação de tokens- é avaliado 1 token para cada 1 token tokenizado
- Como foi programada uma sintaxe separada por espaços e quebras de linha, é possível interpretar o código de forma intuitiva
- O código começa tentando avaliar
5(por não ser builtin, avalia para si mesmo) - O
cranké configurado para executar a cada vez que 5 tokens forem colocados na pilha 2crank,2,crank,1são todos colocados na pilha (ocranknão é executado porque está configurado comcrank 5): 4. 2crank 3. 2 2. crank- 1
cranké o 5º token, então é executado (configuraçãocrank 1)unglue, como builtin, obtém o valor da palavra no topo da pilha usado por1- isto é, ele obtém o ponteiro de função associado ao builtin
crank
- isto é, ele obtém o ponteiro de função associado ao builtin
- A pilha fica assim:
3. 2crank
2. 2
- [CLIB]
- CLIB é o ponteiro de função que aponta para o builtin
crank
- Execução de
swap: 3. 2crank 2. [CLIB]- 2
- Execução de
quote(builtin que cita o topo da pilha): 3. 2crank 2. [CLIB]- [2]
- Execução de
prepose(semelhante aocomposedo Stem, mas colocada antes e chamada de VMACRO): 2. 2crank- ([2] [CLIB])
- Chamada de
def- define a palavra
2crank, colocando2e chamando o ponteiro de função que aponta para o builtincrank
- define a palavra
- É preciso explicar o que é VMACRO e explicar a diferença entre a pilha do Cognition e a pilha do Stem
Diferenças com o Stem
- Na pilha do Stem, é possível colocar palavras diretamente na pilha
- No Cognition, a palavra não é avaliada e é colocada em um container para então ir para a pilha
- No Stem, palavras como
composefuncionam com uma palavra (ou um container com uma única palavra) e outro container - Isso torna a API do Cognition mais consistente
- No Stem, palavras como
- Palavras como
cdtambém exploram esse conceito
Macros
- Outra diferença entre citação do Stem e os containers do Cognition
- Na avaliação de uma macro, tudo dentro da macro é avaliado e o
cranké ignorado - Quando associada a uma palavra, ao avaliar essa palavra,
1 comentários
Comentários do Hacker News
Aqui vai um resumo de alguns comentários principais: