O escopo do ecr dentro do cristal: ou como eu passo em variáveis e Objetos em modelos ECR?
Como principiante na linguagem Crystal, ainda tenho dificuldade em entender alguns dos conceitos nela contidos, e desenvolver uma sensação de codificação em Crystal.
Quando me deparo com problemas difíceis, que eu resolvo ou começo a entender, faço um blog sobre eles, para que outros possam se beneficiar - pois a falta de documentação é sentida severamente (por mim) às vezes.
Avante!
Aqui está a documentação do ECR (no v.0.27, a última versão atual do Crystal):
https://crystal-lang.org/api/0.27.0/ECR.html
Você pode dar uma olhada no código fonte do ECR, os modelos de cristal embutidos, aqui:
https://github.com/crystal-lang/crystal/tree/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr
A ECR é uma linguagem de modelo em tempo de compilação. Não é possível usar o ECR para processar modelos em tempo de execução!
Macros para governar o mundo
Quando você usa os modelos ECR em Crystal ( também se você os usar por meio do Kemal ), você está usando macros.
O Crystal tem uma linguagem de macro que permite abstrair e reutilizar código; Como primeiro passo da compilação, as macros são avaliadas e o código resultante é "colado" no local onde a chamada de macro foi feita.
Depois a Crystal continua a compilar o código.
Como é que isto funciona para a ECR?
Vamos tomar, por exemplo, o def_to_s(nome_de_arquivo) macro no código do exemplo:
exigir "ecr" saudação de classe def initialize(@name : String) final ECR.def_to_s "greeting.ecr" final # saudação.ecr Saudação, ! Greeting.new("John").to_s #=> Saudação, John!
A Crystal vai chamar a macro def_to_s em tempo de compilação, com "greeting.ecr" sendo passado para ele.
A macro é definida aqui:
https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/macros.cr
macro def_to_s(nome_de_arquivo)
def to_s(__io__)
ECR.embed {{{{nome do ficheiro}}, "__io__".
final
final
a sua turma será reescrita desta forma no primeiro passo:
exigir "ecr" saudação de classe def initialize(@nome : String) final def to_s(__io__)
ECR.embed "saudação.ecr", "__io__". final
Viu o que acabou de acontecer? um método to_s foi adicionado à sua classe, que por si só contém uma macro. Vamos ver o que esta macro faz:
macro embed(nome_do_arquivo, io_nome)
\{{{{rrorocesso("ecr/processo", {{{nome do ficheiro}}, {{io_nome.id.stringify}}}}}
final
Esta é a chamada principal da ECR. O que ela faz, é compilar (? / executa) uma aplicação diferente ecr/processo e passa-lhe o nome de ficheiro e io_nome como parâmetros.
O retorno é o resultado da saída dessa aplicação.
O que significa a contrabarra?
"É possível definir um macro que gera uma ou mais definições de macro. Você deve escapar de expressões macro da macro interna precedendo-as com uma barra invertida para evitar que elas sejam avaliadas pela macro externa".
É essencialmente uma macro aninhada!
ecr/processo é definido aqui:
é essencialmente um wrapper em torno do ECR.process_file (lembre-se, isto não é mais uma macro - esta é uma aplicação cuja saída será eventualmente colada no seu código Crystal!)
O ecr/processador é definido aqui:
Ele processa o arquivo, e cria uma cadeia que devolve.
Aqui está um pequeno excerto:
str << nome_tampão
str <<< " << "
string.inspect(str)
str << '\n'
buffer_name é o que passamos ao Crystal acima (__io__ - identificado pelo seu id em io_name.id.stringify).
Os valores de saída e controle também são processados. Além disso, a saída de depuração é colada para você (usando # como um comentário):
append_loc(str, filename, line_number, column_number)
Basicamente, o código que você coloca nos arquivos ECR é colado diretamente no seu código - no local que você especificou. Tudo isso é feito por macros e executando um analisador ECR especial.
O código ECR não tem conhecimento das suas variáveis - se você obtiver falhas de escopo, falhas de variáveis indefinidas, etc., não é devido a você não "ter passado para o ECR", mas devido a não estar no escopo correto.
Tente o que você estava tentando fazer com o código ECR diretamente no seu código principal.
Demonstração
Aqui está uma demonstração de como "usar" uma classe dentro do seu código ECR em Kemal. A classe aninha um trecho adicional de ECR para ser renderizado com o contexto da classe.
o arquivo debug.cr:
precisam de "kemal".
requerem "./.../macros.cr".
módulo Debug
incluir Macrosclasse classe DClass
@teste : Corda
def initialize()
@test = "Test String" (Teste de Cordas)
final
ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
finalget "/debug" do |env|
logado = falso
depuração" do rrender
finalfinal
o arquivo de debug.ecr:
<% content_for “main” do%>
Debug information
<%= loggedin %>
Incorporating decorado snippet
<%= DClass.new() %>
<% end %>
o código relevante para a macro-renderização, definido em macros.cr:
macro mrender(nome do arquivo)
renderizar "src/views/#{{{{{filename}}}.ecr", "src/views/layout.ecr"
final
Ele usa a macro renderização do Kemal que permite especificar um layout para sua visualização. (Você não precisa disso em seu código necessariamente, ele é dado apenas para a completude aqui)
o arquivo src/views/snippets/debug_snippet.ecr:
Desbug snippet
A saída:
A juntar tudo:
- O usuário chama / depura em seu navegador da web
- O macro get de Kemal vai coincidir com a rota "/debug". Está definido no meu debug.cr
- A variável local logado em será falso
- debug.ecr será processado pelas macros ECR, e colado em "debug.cr". (a representação AST), como se tivesse sido directamente introduzida por si
- a variável local logado em será avaliado como falso (em tempo de execução)
- nós chamamos DClass.new()e pedir-lhe a sua representação em cadeia - que é definida pelo método def to_s.
- podemos chamar DClass.new(), porque está definido no mesmo módulo em que estamos executando. Novamente, simplesmente pense como o seu código ECR sendo colado ali mesmo, abaixo da definição da classe.
- pedimos a sua representação em cadeia porque usamos a sintaxe
OK, vamos ver o que acontece no DClass.new call:
classe classe DClass
@teste : Corda
def initialize()
@test = "Test String" (Teste de Cordas)
final
ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
final
na inicialização, uma string @test é definida. Este é um exemplo variável da instância DClass que acabamos de criar. (Você pode ver que é uma variável de instância porque ela tem um "@" na frente dela. Dois "@@" seria uma variável de classe)
Esta variável de instância é usada / exibida em debug_snippet.ecr
Já discutimos como o ECR.def_to_s funciona antes.
Efectivamente, depois de passar pela fase macro, esta turma ficaria algo parecido com isto:
classe classe DClass
@teste : Corda
def initialize()
@test = "Test String" (Teste de Cordas)
final
def to_s(__io__)
__io__ << "Debug snippet\n"
__io__ << ""
__io__ << @test
__io__ << ""
final
final
Usando esta técnica é possível definir classes para renderizar trechos de código ECR, em vez de configurar e passar manualmente cada nome de variável.
Espero que este artigo o ajude, e que você o encontre em vez de ficar frustrado - gostaria de ter algo assim para me orientar no início
Referências
Referência Kemal:
Consulte esta página para obter mais informações sobre macros:
NB: Nós AST: nós de árvore de sintaxe abstrata
- https://crystal-lang.org/reference/syntax_and_semantics/methods_and_instance_variables.html
- https://crystal-lang.org/reference/syntax_and_semantics/class_variables.html
Isto apontou-me na direcção certa, kudos!!