Perangkat makro

NimNode, parseStmt, dan blok quote do sebagai perangkat penulisan makro.

Daftar isi

Membangun AST node demi node dengan newTree memadai untuk makro kecil, tetapi cepat melelahkan ketika pohonnya bertambah. Pustaka macros menyediakan tiga perangkat yang melengkapi satu sama lain, yaitu NimNode beserta properti kind untuk menginspeksi argumen, parseStmt yang mengubah teks menjadi AST, dan blok quote do yang menulis AST dengan sintaks Nim biasa lengkap dengan celah penyisipan. Topik ini mempraktikkan ketiganya pada makro yang sama ukurannya dengan makro nyata.

Menginspeksi argumen dengan kind#

Setiap NimNode membawa kind yang menyatakan jenisnya, dan makro yang menerima untyped dapat memeriksanya sebelum memutuskan bentuk keluaran. Makro berikut meneruskan argumennya apa adanya, tetapi mencetak kind-nya pada waktu kompilasi, sehingga perbedaan bentuk argumen terlihat langsung.

nim perangkat_node.nim
# NimNode: menginspeksi node lewat kind pada waktu kompilasi
import macros

macro periksa(x: untyped): untyped =
  echo "kind: ", x.kind
  result = x

echo periksa(1 + 2 * 3)
echo periksa("teks")
echo periksa([1, 2, 3])
bash
# kompilasi dan jalankan makro penginspeksi kind
nim r -f --hints:off perangkat_node.nim
text Keluaran
kind: nnkInfix
kind: nnkStrLit
kind: nnkBracket
7
teks
[1, 2, 3]

Enam baris pertama keluaran semuanya hasil kompilasi, tiga baris berikutnya hasil program, dan pola dua fase ini khas makro. Properti lain seperti len dan indeks [i] juga tersedia pada NimNode, sehingga makro dapat menelusuri anak-anaknya, menghitung argumennya, dan membangun cabang yang berbeda menurut bentuk masukan. Kombinasi case terhadap kind inilah pola baku makro yang menerima banyak bentuk, yaitu setiap nilai nnk menjadi satu cabang pemeriksaan, dan modul macros menyediakan proc error untuk menolak bentuk yang tidak didukung dengan pesan kesalahan yang dirumuskan sendiri oleh pembuat makro.

Pasangan dari parseStmt adalah parseExpr yang mengurai satu ekspresi alih-alih kumpulan pernyataan, sehingga hasilnya berupa node ekspresi yang siap disisipkan ke posisi yang membutuhkan nilai. Keduanya sering dipakai berdampingan dengan quote do, yaitu bagian tetap ditulis dalam quote, sedangkan bagian yang baru diketahui saat kompilasi disusun lewat penguraian teks lalu disisipkan lewat celah penyisipan.

parseStmt dari teks menjadi AST#

Proc parseStmt menjalankan pengurai Nim terhadap sebuah teks dan mengembalikan AST-nya, sehingga makro dapat menerima kode dalam bentuk string, misalnya dari berkas konfigurasi yang dibaca pada waktu kompilasi. Makro berikut mengurai teks, mencetak pohonnya dengan treeRepr, lalu meneruskannya agar dinyatakan sungguhan pada program.

nim parsestmt.nim
# parseStmt mengubah teks menjadi AST pada waktu kompilasi
import macros

macro muat(kode: static string): untyped =
  let pohon = parseStmt(kode)
  echo pohon.treeRepr
  result = pohon

muat("let x = 40 + 2")
echo x
bash
# kompilasi dan jalankan makro pemuat teks
nim r -f --hints:off parsestmt.nim
text Keluaran
StmtList
  LetSection
    IdentDefs
      Ident "x"
      Empty
      Infix
        Ident "+"
        IntLit 40
        IntLit 2
42

Parameter makro bertipe static string menuntut argumen berupa teks yang diketahui saat kompilasi, dan itulah syarat agar parseStmt dapat bekerja. Pohon hasil penguraian menyimpan deklarasi x lengkap dengan ekspresinya, Empty menjadi penanda bagian yang tidak diisi seperti tipe eksplisit, dan pengenal x kemudian hidup di lingkup pemanggil sebagaimana layaknya kode yang ditulis langsung.

quote do dengan celah penyisipan#

Blok quote do menulis AST dalam bentuk sintaks Nim biasa, dan pengenal yang dibungkus tanda petik terbalik menjadi celah penyisipan yang diganti nilai dari lingkup makro. Perbandingannya dengan newTree nyata, yaitu makro yang sama dapat dibaca tanpa memikirkan jenis node sama sekali. Makro berikut menciptakan proc baru pada waktu kompilasi, lengkap dengan namanya yang disisipkan dari parameter.

nim quote_proc.nim
# quote do menyusun AST dari sintaks Nim biasa
import macros

macro buatPengali(nama: untyped, faktor: int): untyped =
  result = quote do:
    proc `nama`(x: int): int =
      x * `faktor`

buatPengali(tigaKali, 3)
buatPengali(limaKali, 5)

echo tigaKali(10)
echo limaKali(10)
bash
# kompilasi dan jalankan makro pembuat proc
nim r -f --hints:off quote_proc.nim
text Keluaran
30
50

Dua pemanggilan makro melahirkan dua proc berbeda, yaitu tigaKali dan limaKali, dan keduanya sah dipanggil seperti proc yang ditulis manual karena memang menjadi bagian program pada waktu kompilasi. Seluruh tubuh proc di dalam quote do juga ikut menjadi AST hasil, sehingga pemanggil buatPengali sama sekali tidak melihat mekanisme pembangunannya dan hanya menemukan dua proc siap pakai di lingkup modulnya.

Blok quote do menerima lebih dari satu pernyataan, sehingga makro yang menghasilkan deklarasi bertingkat seperti tipe beserta proc pendampingnya tetap ditulis sebagai satu blok kode yang terbaca alami. Untuk pemeriksaan, hasil quote do dapat disisipi echo pada waktu kompilasi atau dibandingkan dengan harapan lewat treeRepr, dan kebiasaan ini menyingkat pencarian kesalahan konstruksi yang mungkin tersembunyi di antara banyak baris bloknya. Pola ini menjadi tulang punggung pustaka yang menjanjikan kode dalam bentuk DSL kecil, misalnya pernyataan routes: pada pustaka web Nim.

Kesalahan umum#

  • Memanggil proc pemeriksa AST pada posisi waktu berjalan. Pernyataan seperti echo treeRepr(3) ditolak dengan Error: type mismatch yang mengharapkan proc treeRepr(n: NimNode): string, karena pemeriksa pohon hanya bermakna pada node AST, dan pemeriksaan yang benar dilakukan di dalam makro terhadap argumennya.
  • Lupa menyisipkan dengan tanda petik terbalik di dalam quote do. Pengenal tanpa penyisipan diperlakukan sebagai nama tetap pada AST hasil, sehingga proc nama(x: int) menciptakan proc bernama nama untuk semua pemanggilan, bukan proc dengan nama dari parameter.
  • Memakai argumen biasa sebagai masukan parseStmt. Parameter makro harus dinyatakan static string agar menuntut teks yang diketahui saat kompilasi, karena teks yang baru ada saat program berjalan tidak dapat dikembalikan menjadi AST pada waktu kompilasi.

Ringkasan#

  • NimNode membawa kind, len, dan akses indeks, sehingga makro dapat menginspeksi dan mencabang menurut bentuk argumennya pada waktu kompilasi.
  • parseStmt mengubah teks yang diketahui saat kompilasi menjadi AST, dan treeRepr mencetak pohonnya untuk pemeriksaan.
  • Blok quote do menulis AST dengan sintaks Nim biasa, penyisipan berlangsung lewat pengenal bertanda petik terbalik, dan hasilnya dapat berupa proc baru yang langsung dipanggil. Ketiga perangkat ini berbagi satu tipe yang sama, yaitu NimNode, sehingga hasil satu perangkat menjadi masukan perangkat lainnya tanpa konversi dan makro yang besar tersusun dari bagian-bagian kecil yang dapat diperiksa satu per satu.

Referensi#