Trabalhar no PEG no Core Developer Sprint

Neste artigo, não falarei sobre os novos recursos do gerador de analisador - eu o descrevi o suficiente nas partes anteriores. Em vez disso, quero contar o que fiz no Core Developer Sprint na semana passada antes que tudo seja apagado da minha memória. Embora a maior parte do material esteja de alguma forma relacionada ao PEG. Então, eu tenho que mostrar algum código que define a direção na implementação do analisador PEG para Python 3.9.



Todos os anos, nos últimos quatro anos, a equipe de desenvolvimento principal do Python se reúne para um sprint semanal em um local exótico. Esses sprints são patrocinados pelo anfitrião e pelo PSF. Nos primeiros dois anos, visitamos o Facebook em Mountain View, no ano passado a Microsoft tinha uma fila em Bellevue e a Bloomberg em Londres foi escolhida para este sprint. (Devo dizer que parece bem legal.) Glória ao desenvolvedor principal Pablo Galindo Salgado pela organização!


Desta vez, mais de 30 desenvolvedores, bem como dois Padawans, nos reuniram. As pessoas trabalharam em várias partes: de 3,8 bloqueadores a novos PEPs. Espero que o blog do PSF sobre nossas realizações. Um dos pontos principais foi que o número de PRs abertos era inferior a 1000, mais de 100 PRs aguardavam sua revisão. Houve até uma competição com uma tabela de líderes - os 10 principais participantes que realizaram um número maior de relações públicas de outras pessoas.


Para mim, sempre o principal motivo para participar de tais eventos é uma reunião com pessoas com as quais colaboro on-line ao longo do ano, mas com quem vejo apenas uma ou duas vezes por ano. Esse sprint foi na Europa, então vimos uma composição um pouco diferente, e isso foi especialmente importante. Apesar disso, também trabalhei de maneira bastante produtiva, sobre a qual falarei.


Na maior parte do tempo no sprint, trabalhei com Pablo e Emily Morhouse em um gerador de analisador baseado em PEG, que espero que algum dia substitua o atual gerador de analisador baseado em pgen. Este não é o mesmo código que o gerador sobre o qual escrevi, mas é bem parecido. Pablo já contribuiu para esta versão.


No primeiro dia do sprint, segunda-feira, trabalhei principalmente nos artigos 7 e 8 deste ciclo. Inicialmente, planejei publicá-los juntos, mas não tive tempo até o final do dia. Então, ele o dividiu em duas partes e publicou a primeira metade dedicada à criação do metagrama. Na tarde de sexta-feira, finalmente encontrei algum tempo para terminar a parte 8. No entanto, ainda tinha que omitir a história porque ainda não tinha um bom exemplo.


Na terça-feira, comecei a trabalhar na gramática PEG para Python. É ainda mais próximo do código do que da especificação abstrata antes de adicionar ações. Entendemos que precisávamos verificar a gramática desenvolvida no código Python real. Então, enquanto eu terminava minha gramática, Emily estava fazendo scripts para testes em lote. Depois disso, meu fluxo de trabalho ficou mais ou menos assim:


  1. Execute um script para testar algum diretório com código Python
  2. Investigar o primeiro problema em que ele caiu
  3. Gramática correta para resolver este problema
  4. Repita até que não haja problemas
  5. Ir para o próximo diretório

Comecei com meu próprio código de projeto pgen. No final, minha gramática foi capaz de analisar todas as construções Python usadas no pgen, e fui para os módulos de biblioteca padrão. Primeiro, enfocando Lib/test , depois Lib/asyncio e Lib/asyncio finalmente, Lib , que é, de fato, toda a biblioteca padrão (pelo menos a escrita em Python). No final da semana, pude comemorar: os únicos arquivos da biblioteca padrão em que o novo analisador caíam eram arquivos com más codificações. Eles existem apenas como dados de teste para verificar se os problemas de codificação serão tratados da maneira correta; e alguns arquivos para Python 2 que são necessários como casos de teste para lib2to3 .


Em seguida, adicionei algum código para calcular o tempo de execução do analisador no script de Emily, e parece que o novo analisador PEG é um pouco mais rápido que o analisador pgen antigo. Isso não significa que as coisas vão além! Existem mais de 100 regras na gramática (160 linhas) e, para gerar a AST, precisamos adicionar uma ação a cada uma (consulte a Parte 6).


Anteriormente, realizei um experimento para ver quanto aumentaria o tamanho do arquivo após adicionar ações. Eu cheguei à conclusão de que ele se tornará 2-3 vezes maior, e aqui está a gramática deste experimento:


 start[mod_ty]: a=stmt* ENDMARKER{ Module(a, NULL, p->arena) } stmt[stmt_ty]: compound_stmt | simple_stmt compound_stmt[stmt_ty]: pass_stmt | if_stmt pass_stmt[stmt_ty]: a='pass' NEWLINE { _Py_Pass(EXTRA(a, a)) } if_stmt[stmt_ty]: | 'if' c=expr ':' t=suite e=[else_clause] { _Py_If(c, t, e, EXTRA(c, c)) } else_clause[asdl_seq*]: | 'elif' c=expr ':' t=suite e=[else_clause] { singleton_seq(p, _Py_If(c, t, e, EXTRA(c, c))) } | 'else' ':' s=suite { s } suite[asdl_seq*]: | a=simple_stmt { singleton_seq(p, a) } | NEWLINE INDENT b=stmt+ DEDENT { b } simple_stmt[stmt_ty]: a=expr_stmt NEWLINE { a } expr_stmt[stmt_ty]: a=expr { _Py_Expr(a, EXTRA(a, a)) } expr[expr_ty]: | l=expr '+' r=term { _Py_BinOp(l, Add, r, EXTRA(l, r)) } | l=expr '-' r=term { _Py_BinOp(l, Sub, r, EXTRA(l, r)) } | term term[expr_ty]: | l=term '*' r=factor { _Py_BinOp(l, Mult, r, EXTRA(l, r)) } | l=term '/' r=factor { _Py_BinOp(l, Div, r, EXTRA(l, r)) } | factor factor[expr_ty]: | l=primary '**' r=factor { _Py_BinOp(l, Pow, r, EXTRA(l, r)) } | primary primary[expr_ty]: | f=primary '(' e=expr ')' { _Py_Call(f, singleton_seq(p, e), NULL, EXTRA(f, e)) } | atom atom[expr_ty]: | '(' e=expr ')' { e } | NAME | NUMBER | STRING 

Há toneladas de coisas aqui que eu tenho que explicar.


  • As ações são escritas em C. Como no gerador Python da parte 6, cada uma delas é uma expressão.
  • O texto entre colchetes imediatamente após o nome da regra determina o tipo de resultado para o método de regra correspondente. Por exemplo, atom[expr_ty] significa que expr_ty será retornado para atom . Se você observar o Include/Python-ast.h no repositório CPython, verá que esse tipo é a estrutura usada para representar expressões no AST interno.
  • Se a alternativa tiver apenas um elemento, a ação poderá ser omitida, pois o comportamento padrão é simplesmente retornar o nó AST resultante. Caso contrário, a ação deve ser especificada explicitamente.
  • O código C gerado também precisa de algum processamento. Por exemplo, supõe-se que alguns arquivos de cabeçalho CPython serão incluídos. Por exemplo, onde o tipo expr_ty , bem, e muitas outras coisas necessárias.
  • A variável p contém um ponteiro para a estrutura do Parser usada pelo analisador gerado. (E sim, isso significa que é melhor não especificar um único elemento na regra p - caso contrário, o código C gerado não será compilado!)
  • EXTRA(node1, node2) é uma macro que se expande em um conjunto de argumentos adicionais que devem ser passados ​​para cada função de construção AST. Isso economiza muito tempo ao escrever uma ação - caso contrário, você teria que especificar o número da linha inicial e final, o deslocamento da coluna e também um ponteiro para a arena usada para distribuição. (Nós AST não são objetos Python e usam um layout mais eficiente.)
  • Devido a algum comportamento interessante do pré-processador C em EXTRA() , não podemos usar macros para criar um nó AST, mas devemos usar funções básicas. É por isso que você vê, por exemplo, _Py_Binop(...) , não Binop(...) . No futuro, pensarei em como resolvê-lo de maneira diferente.
  • Para repetir elementos ( foo* ou foo+ ), o gerador de código cria uma regra auxiliar do tipo asdl_seq* . Essa é a estrutura de dados que o AST usa para representar repetições. Em vários lugares, precisamos criar essa repetição a partir de apenas um elemento e, para isso, definimos a função auxiliar singleton_seq() .

Talvez algo disso pareça estranho, e não vou discutir. Este é um protótipo e seu principal objetivo é demonstrar que, em princípio, é possível gerar um AST funcional usando um analisador gerado a partir de uma gramática PEG. Tudo isso funciona sem nenhuma alteração no compilador de tokenizer ou bytecode existente. Um protótipo pode compilar expressões simples e if , e o AST resultante pode ser compilado no bytecode e executado.


Outras coisas que fiz como parte deste sprint:


  • Convenci Lukasz Lang a mudar o PEP 585 (sua proposta para o futuro tipo de dica) para focar nos genéricos, e não em um conjunto de idéias, como era antes. O novo PEP parece muito melhor e, na reunião de digitação de vários dias atrás, onde representantes de desenvolvedores de utilitários de verificação de tipo Python (mypy, pytype e Pyre) estavam presentes, ele recebeu aprovação geral. (Não é o mesmo que endosso pelo Conselho do BCE!)
  • Ajudou Yuri Selivanov a desenvolver uma API para o tipo de mapa congelado , que ele queria adicionar ao stdlib. Vários outros colaboradores também contribuíram para o design - acho que terminamos o sprint com algumas placas cheias de exemplos e fragmentos de API. O resultado é PEP 603 e está atualmente em discussão ativa . (Uma observação: uma implementação do tipo de dados proposto já existe no CPython, como parte da implementação do PEP 567 , o módulo contextvars . Essa é uma estrutura de dados muito interessante, o Hash Array Mapped Trie , que combina a tabela de hash com a árvore de prefixos)
  • Yuri, como sempre, está cheio de idéias. Ele também trabalhou em grupos de exceção (uma idéia do Trio ) que ele queria implementar em assíncrono no Python 3.9. O PEP não parecia estar lá da última vez que olhei, mas definitivamente me lembro de uma placa cheia de diagramas.
  • Temos discutido ativamente a proposta de Lucas para um ciclo mais curto de lançamento do Python. Isso resultou no PEP 602 , no qual ele sugere fazê-los anualmente, em outubro. (Há uma boa razão para isso: isso se deve ao cronograma típico das conferências e sprints principais do Python.) Isso ainda é muito discutido . Há pelo menos duas contra-ofertas: no PEP 598, Nick Coglan oferece lançamentos de dois anos, permitindo novos recursos nos patches; Steve Dower também gostaria de ver lançamentos bienais, mas sem esse recurso (ainda não havia PEP).
  • Três membros do Conselho de Governadores que participaram do sprint (Brett Cannon, Carol Willing e eu) se reuniram e discutiram nossa visão para o desenvolvimento futuro do núcleo do Python. (Não quero falar muito sobre isso, porque planejamos falar sobre isso no próximo PyCon nos EUA. No entanto, provavelmente sugerimos o início da angariação de fundos para que o PSF possa contratar vários desenvolvedores para apoiar e acelerar o desenvolvimento do kernel).
  • Tive uma interessante conversa de almoço com Joanna Nanjeki - uma das pessoas que compareceu à cerimônia. Ela contou a história de como descobriu a Internet quando tinha 8 anos e levou o irmão mais novo a um cibercafé enquanto a mãe trabalhava. Lá, ela descobriu o Google e o e-mail e ficou na primeira semana.
  • O principal evento alcoólico da semana foram alguns coquetéis do Zombie Apocalypse, que alguns de nós pedimos no bar Alchemist. Servido em um balão Erlenmeyer de 2 litros com uma grande quantidade de fumaça falsa resultante do vazamento de álcool em uma mistura regular de álcool, cada bebida é projetada para quatro pessoas.
  • Na sexta-feira à noite, Lisa Roach nos levou a um bom restaurante indiano perto de seu hotel. Foi através de quatro estações de metrô, o que foi uma verdadeira aventura (era hora do rush, e quase perdemos Christian Hymes várias vezes). A comida valeu a pena!
  • Em algum momento, tiramos uma foto de grupo. Parece bem futurista, mas é realmente uma paisagem de Londres.


No próximo artigo, espero compartilhar algum progresso com as ações para criar nós AST.


Licença para este artigo e código citado: CC BY-NC-SA 4.0

Source: https://habr.com/ru/post/pt471994/


All Articles