De reikwijdte van ecr binnen kristal: of hoe geef ik variabelen en Objecten door in ECR-sjablonen?

Als een beginner in de Crystal taal worstel ik nog steeds met het doorgronden van sommige concepten en het ontwikkelen van een gevoel voor coderen in Crystal.

Als ik moeilijke problemen tegenkom, die ik oplos of begin te begrijpen, blog ik erover, zodat anderen er hun voordeel mee kunnen doen - want gebrek aan documentatie wordt (door mij) soms ernstig gevoeld.

Vooruit!

Hier is de documentatie voor ECR (in v.0.27, de momenteel laatste versie van Crystal):

https://crystal-lang.org/api/0.27.0/ECR.html

U kunt de broncode van ECR, de ingebedde kristalsjablonen, hier bekijken:

https://github.com/crystal-lang/crystal/tree/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr

ECR is een compileer-tijd sjabloon taal. Je kunt ECR niet gebruiken om templates te verwerken in runtime!

Macro's om de wereld te regeren

Wanneer u de ECR-sjablonen in Crystal gebruikt (ook als u ze gebruikt door middel van Kemal), gebruikt u macro's.

Crystal heeft een macro-taal waarmee code kan worden geabstraheerd en hergebruikt; als eerste stap van het compileren worden de macro's geëvalueerd en de resulterende code wordt "ingeplakt" op de plaats waar de macro-aanroep heeft gestaan.

Dan gaat Crystal verder met het compileren van de code.

Hoe werkt dit voor ECR?

Laten we, bijvoorbeeld, de def_to_s(bestandsnaam) macro in de code van het voorbeeld:

vereisen "ecr"

klasse Begroeting
  def initialiseren(@naam : String)
  einde

  ECR.def_to_s "greeting.ecr"
end

# groet.ecr
Begroeting, !

Begroeting.new("John").to_s #=> Begroeting, John!

Crystal zal de macro aanroepen def_to_s bij het compileren met "greeting.ecr" er in.
De macro wordt hier gedefinieerd:
https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/macros.cr

macro def_to_s(bestandsnaam)

  def to_s(__io__)

     ECR.embed {{filename}}, "__io__"

  einde

einde

zal je klas zo herschreven worden in de eerste stap:

vereisen "ecr"

klasse Begroeting
  def initialiseren(@naam : String)
  einde

  def to_s(__io__)
    ECR.embed "greeting.ecr", "__io__"
einde

Zie je wat er net gebeurde? Een to_s methode werd toegevoegd aan je klasse, die zelf een macro bevat. Laten we eens kijken wat deze macro doet:

macro insluiten(bestandsnaam, io_naam)

    \{{ run("ecr/process", {{filename}}, {{io_name.id.stringify}}) }}

einde

Dit is de kern oproep van ECR. Wat het doet, is het compileert (? / voert) een andere toepassing ecr/proces en geeft bestandsnaam en io_name door als parameters.

De terugkeer is het resultaat van de uitvoer van die toepassing.

Wat betekent de backslash?

"Het is mogelijk om een macro die een of meer macrodefinities genereert. U moet aan macro-expressies van de binnenste macro ontsnappen door ze vooraf te laten gaan door een backslash-teken om te voorkomen dat ze door de buitenste macro worden geëvalueerd."

Het is in wezen een geneste macro!

ecr/proces wordt hier gedefinieerd:

https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/process.cr

het is in wezen een omhulsel rond ECR.process_file (onthoud, dit is geen macro meer - dit is een toepassing waarvan de uitvoer uiteindelijk in je Crystal code zal worden geplakt)!

ecr/processor wordt hier gedefinieerd:

https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/processor.cr

Het verwerkt het bestand, en maakt een string die het teruggeeft.

Hier is een klein uittreksel:

str << buffer_naam

str << " << "

string.inspect(str)

str << '\n'

buffer_name is wat we hierboven aan Crystal hebben doorgegeven (__io__ - geïdentificeerd door zijn id in io_name.id.stringify).

Uitvoer- en controlewaarden worden ook verwerkt. Bovendien wordt debug output voor u ingeplakt (met # als commentaar):

append_loc(str, bestandsnaam, regel_nummer, kolom_nummer)

In principe wordt de code die u in de ECR-bestanden zet, direct in uw code geplakt - op de plaats die u hebt aangegeven. Het wordt allemaal gedaan door macro's en het draaien van een speciale ECR parser.

De ECR code is zich niet bewust van uw variabelen - als u scope fouten krijgt, ongedefinieerde variabele fouten, etc, is het niet te wijten aan het feit dat u het niet "in ECR heeft doorgegeven", maar omdat het niet in de juiste scope is.

Probeer wat je probeerde te doen met de ECR code direct in je hoofdcode.

Demonstratie

Hier is een demonstratie hoe u een klasse kunt "gebruiken" binnen uw ECR code in Kemal. De klasse nest een extra ECR-fragment dat moet worden gerenderd met de context van de klasse.

het bestand debug.cr:

require "kemal"

require "./../macros.cr"

module Debug
   Macro's omvatten

      klasse DClass
           @test : String
           def initialiseren()
               @test = "Test String"
           einde
           ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
       einde

  get "/debug" do |env|

    ingelogd = vals

    mrender "debug"
   einde

einde

het bestand debug.ecr:


Debug informatie


Gedecoreerd knipsel

de relevante code voor de macro mrender, gedefinieerd in macros.cr:

macro mrender(bestandsnaam)
   render "src/views/#{{filename}}.ecr", "src/views/layout.ecr"
einde   

Het gebruikt Kemal's macro render die je toelaat om een layout te specificeren voor je view. (Je hebt dit niet per se nodig in je code, het is hier alleen gegeven voor de volledigheid)

het bestand src/views/snippets/debug_snippet.ecr:

Debug snippet

De uitgang:

afbeelding

Alles op een rijtje zetten:

  1. De gebruiker roept /debug op in zijn webbrowser
  2. Kemal 's macro get zal overeenkomen voor de route "/debug". Het is gedefinieerd in mijn debug.cr
    1. De lokale variabele ingelogd zal op vals worden gezet
    2. debug.ecr zal worden verwerkt door de ECR macro's, en geplakt in "debug.cr" (de AST representatie), alsof het direct door u was ingevoerd
      1. de lokale variabele ingelogd zal worden geëvalueerd als onwaar (bij run-time)
      2. noemen we DClass.new()en vraag het om zijn string-representatie - die wordt gedefinieerd door de def to_s methode.
        1. kunnen we DClass.new() aanroepen, omdat deze gedefinieerd is in dezelfde module, als waarin we aan het uitvoeren zijn. Nogmaals, denk gewoon dat je ECR code daar wordt geplakt, onder de klasse definitie.
        2. we vragen het om zijn string representatie omdat we de syntax gebruiken

OK, laten we eens kijken wat er gebeurt in de DClass.new oproep:

      klasse DClass
          @test : String
           def initialiseren()
               @test = "Test String"
           einde
           ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
       einde

bij initialisatie wordt een string @test ingesteld. Dit is een instantie variabele van de DClass instantie die we zojuist hebben aangemaakt. (Je kunt zien dat het een instantievariabele is omdat er één "@" voor staat. Twee "@@" zou een klassevariabele zijn)

Deze instance variabele wordt gebruikt / weergegeven in debug_snippet.ecr

We hebben eerder besproken hoe ECR.def_to_s werkt.

Na het doorlopen van het macrostadium zou deze klasse er ongeveer zo uitzien:

      klasse DClass
          @test : String
           def initialiseren()
               @test = "Test String"
           einde
           def to_s(__io__)

                __io__ << "Debug snippet"

                __io__ << ""

                __io__ << @test

                __io__ << ""

           einde
       einde

Met deze techniek kunt u klassen definiëren om fragmenten van ECR code te renderen, in plaats van elke variabelenaam handmatig op te zetten en door te geven.

Ik hoop dat dit artikel je helpt, en dat je het zult vinden in plaats van gefrustreerd te raken - ik wou dat ik zoiets had om me te begeleiden bij het beginnen Glimlach

Referenties

Kemal referentie:

Raadpleeg deze pagina voor meer informatie over macro's:

NB: AST-knooppunten: abstracte syntaxisboomknooppunten

Dit wees me in de juiste richting, kudos!!