3 pontos por GN⁺ 2024-05-03 | 1 comentários | Compartilhar no WhatsApp

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
      1. introdução de caracteres de colchete esquerdo/direito (na prática, notação prefixada)
      2. 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)
  • 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
  • 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 eval no Stem)
  • f é o Falias padrão, executa o topo da pilha d sem ser colocado na pilha
  • d altera a lista de delimitadores para o valor de string da palavra (ou seja, exclui apenas o caractere l dos 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 l deixe de ser delimitador
gldftgldfdtgldf  dfiff1 crank f
  • Como d é delimitador, gl é colocado na pilha e o Falias f é chamado, fazendo gl não ser delimitador
  • tgl é colocado na pilha e df faz com que tg deixe de ser delimitador
  • dtgl é colocado na pilha e \ndf faz com que o \n seja 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 \n sã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
    1. [caractere de espaço] \n
  • df define \ \n como não-delimitador
  • if define \ \n como ignorado (ignorados no início da tokenização)
  • f executa dtgl e armazena o toggle dflag que 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 palavra crank é tokenizada e executada por f (1 é tratado como número nesse caso, mas no Cognition tudo é palavra)
  • Sequência de bootstrap concluída! O que o crank faz é 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 crank recebe 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 de f durante 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, 1 são todos colocados na pilha (o crank não é executado porque está configurado com crank 5): 4. 2crank 3. 2 2. crank
    1. 1
  • crank é o 5º token, então é executado (configuração crank 1)
  • unglue, como builtin, obtém o valor da palavra no topo da pilha usado por 1
    • isto é, ele obtém o ponteiro de função associado ao builtin crank
  • A pilha fica assim: 3. 2crank 2. 2
    1. [CLIB]
    • CLIB é o ponteiro de função que aponta para o builtin crank
  • Execução de swap: 3. 2crank 2. [CLIB]
    1. 2
  • Execução de quote (builtin que cita o topo da pilha): 3. 2crank 2. [CLIB]
    1. [2]
  • Execução de prepose (semelhante ao compose do Stem, mas colocada antes e chamada de VMACRO): 2. 2crank
    1. ([2] [CLIB])
  • Chamada de def
    • define a palavra 2crank, colocando 2 e chamando o ponteiro de função que aponta para o builtin crank
  • É 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 compose funcionam com uma palavra (ou um container com uma única palavra) e outro container
    • Isso torna a API do Cognition mais consistente
  • Palavras como cd també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

 
GN⁺ 2024-05-03
Comentários do Hacker News

Aqui vai um resumo de alguns comentários principais:

  • A explicação sobre o próprio projeto Cognition aparece tarde demais na introdução do documento. Seria melhor apresentar primeiro o conteúdo mais importante para poupar o tempo do leitor.
  • Existem outras abordagens já existentes, como a configuração da camada reader do Racket, que expandem a sintaxe enquanto mantêm a interoperabilidade. Há dúvida se a abordagem do Cognition é fundamentalmente "melhor".
  • No Common Lisp também dá para alterar a sintaxe livremente com reader macro, macro e compiler macro. O ponto central da metaprogramação é lidar com a semântica, não com a sintaxe.
  • A capacidade do Cognition de definir e redefinir estruturas sintáticas em tempo de execução, entrando e saindo delas, é linda e interessante. Abre a possibilidade de construir uma máquina que realmente "pense".
  • Sintaxe é o que dá estrutura, então é contraditório dizer que ela deve ser removida. Sintaxe excessivamente concisa pode atrapalhar a legibilidade e a compreensão.
  • A forma de redação do texto em si é um pouco prolixa e com tom sarcástico, o que dificulta a leitura. Mas ele trata de questões profundas.