2 poin oleh GN⁺ 2024-11-03 | 1 komentar | Bagikan ke WhatsApp
  • Saat menangani 42 bahasa untuk membuat syntax highlighter baru bagi llamafile, terungkap bahwa bahkan leksing sederhana pun dipenuhi pengecualian per bahasa dan sintaks lama yang tersembunyi rapat
  • Implementasinya menggunakan C++ dan GNU gperf untuk mempercepat pencarian keyword, sementara pemrosesan yang berfokus pada string, komentar, dan keyword diselesaikan dengan finite state machine berbasis loop for dan switch
  • Trigraph C, terminator baris u2028 dan u2029 JavaScript, heredoc di Shell/Perl/Ruby, serta interpolasi string di Kotlin/Scala/TypeScript/Swift berulang kali muncul sebagai kasus yang sulit ditangani hanya dengan leksing
  • Dari sisi jumlah baris kode, FORTH hanya 125 baris sementara Ruby mencapai 1042 baris; pada Ruby, operator <<, heredoc, dan backquote saling berbenturan sehingga highlighting tanpa parsing menjadi sangat sulit
  • Highlighter baru llamafile didemokan di Windows 10 dengan Meta LLaMA 3.2 3B Instruct dan juga bisa berjalan di macOS, Linux, FreeBSD, serta NetBSD, sehingga membedakannya dari ollama yang tidak memiliki syntax highlighting

Alasan membuat highlighter llamafile

  • Selama satu bulan mempelajari 42 bahasa pemrograman untuk membuat syntax highlighter baru bagi llamafile
  • Target dukungannya adalah Ada, Assembly, BASIC, C, C#, C++, COBOL, CSS, D, FORTH, FORTRAN, Go, Haskell, HTML, Java, JavaScript, Julia, JSON, Kotlin, ld, LISP, Lua, m4, Make, Markdown, MATLAB, Pascal, Perl, PHP, Python, R, Ruby, Rust, Scala, Shell, SQL, Swift, Tcl, TeX, TXT, TypeScript, dan Zig
  • Daftar ini mencakup sebagian besar TIOBE Index, tetapi Scratch dikecualikan dari target highlighting karena menggunakan blok, bukan teks

Cara implementasi: gperf dan finite state machine

  • Bottleneck terbesar pada syntax highlighter dasar adalah titik ketika perbandingan string berulang bertambah saat menentukan apakah token adalah keyword
  • Menggunakan C++ dan GNU gperf untuk menghasilkan perfect hash table
    • Contoh input gperf mendefinisikan konstanta Java seperti true, false, dan null sebagai keyword
    • File C yang dihasilkan gperf dapat membuat fungsi hash yang hanya mempertimbangkan satu karakter untuk lookup tanpa collision
  • Highlighting C dapat diproses pada kecepatan 35 MB/s berkat gperf meskipun mendefinisikan sekitar 4.000 keyword
  • Sebagian besar pemrosesan lainnya cukup ditangani dengan finite state machine
    • Highlighter dasar bisa dibuat hanya dengan loop for dan switch, tanpa flex, bison, atau ragel
    • Jika berfokus pada string, komentar, dan keyword, umumnya bisa ditangani pada level leksing
    • Untuk menyorot elemen seperti nama fungsi C, parsing sungguhan mungkin diperlukan
  • highlight_ada.cpp digunakan sebagai contoh implementasi

Demo llamafile dan lingkungan penggunaan

  • Highlighter baru dan antarmuka chatbot meningkatkan kegunaan llamafile, dan syntax highlighting yang tidak ada di ollama menjadi pembeda
  • Demo dijalankan di Windows 10 dengan model Meta LLaMA 3.2 3B Instruct
  • llamafile tersebut juga dapat berjalan di macOS, Linux, FreeBSD, dan NetBSD
  • Seiring meningkatnya kualitas model open-weight seperti gemma 27b it, dorongan untuk menggunakan Claude menjadi berkurang

C: pengecualian leksing yang berbeda dari kesan sebagai bahasa sederhana

  • Berbeda dari kesan bahwa C itu sederhana, elemen leksikalnya tergolong sangat unik
  • Trigraph dapat mengganti karakter seperti #, [, \, ^, {, |, }, ~ dengan ??=, ??(, ??/, ??), ??', ??<, ??!, ??>, ??-
    • Sudah dihapus dari standar C23, tetapi karena software legacy, compiler tampaknya akan terus mendukungnya
    • Syntax highlighter yang baik juga harus menangani sintaks ini
  • Universal character pada C dapat merepresentasikan identifier Unicode di dalam source code ASCII, seperti int \uFEB2 = 1;
    • GCC mengeluarkan error jika bukan plane Unicode tertentu yang diizinkan komite standar
  • Komentar satu baris C dapat membentang ke beberapa baris jika menggunakan backslash di akhir baris
    • Bahasa yang mengizinkan escape backslash di source code seperti Perl, Ruby, dan Shell pun tidak mendukung perilaku ala C ini
    • Tcl dan GNU Make dipahami mendukung perilaku ini
    • Emacs dan Pygments kadang menanganinya dengan salah, sedangkan Vim tampaknya selalu menangani backslash dengan benar
  • C juga memiliki null preprocessor directive
    • File .c dari source code v6 awal sering diawali dengan satu baris #
    • Ini masih merupakan kode yang valid, dan dapat digunakan agar preprocessor menghapus komentar tertentu meskipun pelestarian komentar diminta dengan cc -C -E

Sintaks komentar: Haskell dan D

  • Di C, komentar multi-baris tidak dapat menyarang komentar multi-baris lain di dalamnya
  • Haskell mendukung komentar bersarang dalam bentuk {- ... {- ... -} ... -}
  • D mengadopsi komentar // dan /* ... */ dari C apa adanya, serta memperkenalkan sintaks komentar rekursif terpisah dalam bentuk /+ ... +/
  • Dokumen lexical grammar D formal dan terperinci sehingga menyediakan informasi yang diperlukan untuk implementasi
    • Dokumen lexical syntax D membahas detail seperti hex string dan heredoc string
    • D memiliki beragam bentuk string seperti "...", string backtick, r"...", q"...", x"...", dan lainnya

Tcl dan JavaScript: tanda kutip dan terminator baris yang tak terlihat

  • Di Tcl, tanda kutip dapat masuk ke dalam identifier
    • puts a"b mencetak a"b
    • Nama variabel juga dapat memuat tanda kutip, tetapi saat direferensikan harus menggunakan notasi ${a"b}, bukan $a"b
  • JavaScript memiliki lexical grammar bawaan untuk regular expression
    • Seperti /[/]/g, / di dalam tanda kurung character set tidak perlu di-escape
    • Cara yang hanya memindai slash penutup dapat keliru pada kode yang sudah diminify
  • ECMAScript mendefinisikan u2028 LINE SEPARATOR dan u2029 PARAGRAPH SEPARATOR sebagai terminator baris
    • Karakter ini pada dasarnya berperilaku seperti \n
    • Karena merupakan karakter Trojan Source, Emacs dikonfigurasi agar menampilkannya masing-masing sebagai dan
    • Banyak software tidak mengenali karakter ini sehingga kadang merendernya sebagai tanda tanya
    • Disebutkan bahwa selain D, tidak diketahui ada bahasa lain yang melakukan pemrosesan seperti ini
  • Dengan memanfaatkan karakteristik ini, sebuah polyglot C dan JavaScript dapat dibuat di SectorLISP
    • lisp.js berjalan di browser dan juga dapat dikompilasi dengan GCC untuk dijalankan secara lokal
    • llamafile menyorot kode seperti ini dengan benar, tetapi pemrosesan yang sama tidak ditemukan pada highlighter lain

Kasus sudut heredoc di Shell

  • Heredoc Shell menulis string multi-baris dalam bentuk cat <<EOF ... EOF
  • Sintaks quoted heredoc cat <<'END' menonaktifkan substitusi variabel
  • Jika marker heredoc dibuat sebagai string kosong, heredoc berakhir pada baris kosong berikutnya
    • Program contoh mencetak hello dan world dalam dua baris
  • Dalam bahasa yang mendukung heredoc seperti Shell, Ruby, dan Perl, beberapa heredoc dapat diletakkan pada baris yang sama
  • Di Shell, seperti Tcl, # tidak selalu memulai komentar
    • Dalam ${x#hi-}, # digunakan untuk menghapus prefix, dan contohnya mencetak there

Interpolasi string: bahasa yang membutuhkan stack status

  • String Kotlin dapat masuk ke status lain pada { karena interpolasi string, meskipun dimulai dengan "
  • TypeScript, Swift, Kotlin, dan Scala sangat mendukung interpolasi string ke arah memasukkan kode sungguhan ke dalam string
  • Untuk menyorot Kotlin, Scala, dan TypeScript, perlu menghitung kurung kurawal dan mempertahankan stack status parser
    • TypeScript relatif sederhana sehingga cukup menambahkan beberapa status pada finite state machine
    • Kotlin dan Scala mendukung interpolasi baik pada sintaks double quote maupun triple quote, sehingga untuk leksing string saja diperlukan sekitar 13 status independen
    • Swift mendukung sintaks interpolasi "\(var)" dan triple quote, tetapi implementasinya membutuhkan 10 status

Swift, C#, FORTH: perbedaan cara membungkus string

  • Swift mendukung sintaks yang membungkus string "...", """...""", dan /regex/ dengan jumlah # arbitrer
    • Jumlah # di kedua sisi harus cocok
    • Cara ini menyelesaikan masalah memasukkan tanda kutip atau batas regular expression ke dalam string
  • C# mirip dengan string multi-baris triple quote Python, tetapi dapat menggunakan jumlah tanda kutip yang lebih banyak pada awal dan akhir dengan jumlah yang sama
    • Jumlah tanda kutip di sisi kiri menjadi syarat penutup di sisi kanan
    • Dengan mengizinkan jumlah tanda kutip arbitrer, aturan validitas berkurang dibandingkan triple quote klasik Python sehingga dianggap lebih mudah didekode dengan finite state machine
  • FORTH mentokenisasi semuanya berdasarkan batas spasi
    • Sintaks awal string c" juga merupakan satu token
    • c" hello world" memiliki arti yang sama dengan "hello world" di bahasa lain

FORTRAN dan COBOL: aturan kolom tetap

  • llamafile disajikan sebagai use case yang dapat membantu pemeliharaan sistem perbankan bahkan setelah programmer FORTRAN dan COBOL pensiun
  • Disebutkan bahwa di lingkungan air-gapped, kita dapat meminta kode COBOL dan FORTRAN kepada AI yang terkendali seperti Gemma 27b
  • Aturan kolom tetap FORTRAN adalah sebagai berikut
    • Jika *, c, atau C diletakkan di kolom 1, baris tersebut menjadi komentar
    • Jika karakter non-spasi diletakkan di kolom 6, baris yang melebihi 80 karakter dapat dilanjutkan
    • Jika angka diletakkan di kolom 1–5, itu menjadi label
  • Aturan COBOL adalah sebagai berikut
    • Jika * diletakkan di kolom 7, itu menjadi komentar
    • Jika - diletakkan di kolom 7, baris yang melebihi 80 karakter dapat dilanjutkan
    • Kolom 1–6 berisi nomor baris

Zig dan Lua: solusi berbeda untuk string multi-baris

  • Zig memiliki sintaks string multi-baris yang dimulai dengan dua backslash
    • Sintaks ini menghilangkan kebutuhan memanggil textwrap.dedent() pada string triple quote Python
    • Kekurangannya, semicolon dianggap kurang enak dilihat
    • Diusulkan sebagai sintaks string yang layak dipertimbangkan oleh bahasa yang tidak membutuhkan semicolon seperti Go, Scala, dan Python
  • String multi-baris Lua berbasis [[...]], dan dapat memasukkan jumlah = arbitrer di antara tanda kurung siku
    • Jumlah = pada awal dan akhir dicocokkan seperti [==[ ... ]==]
    • Cara yang sama dapat digunakan untuk komentar
    • Bentuk --[[ ... ]] dan --[==[ ... ]==] sama-sama memungkinkan

Assembly: gabungan dialek dan preprocessor

  • Assembly adalah salah satu bahasa yang sulit di-highlight karena terpecah menjadi banyak dialek
  • llamafile bertujuan menangani beberapa sintaks seperti AT&T dan nasm secara masuk akal
  • Jika keyword diperlakukan sebagai identifier pertama pada baris dan bukan item yang muncul setelah colon, sebagian besar kode assembly dapat terlihat cukup baik
  • Sintaks komentar juga tidak sederhana
    • Komentar UNIX awalnya hanya membutuhkan satu /
    • GNU as masih mendukung komentar ini hanya di posisi awal baris
    • Clang disebut tidak mendukung fixed comment sehingga sulit digunakan secara praktis di kode open-source
  • Assembler UNIX awalnya tidak menggunakan tanda kutip penutup pada literal karakter
    • 'x merepresentasikan nilai karakter x, yaitu 0x78
    • GNU as terus mendukungnya, tetapi LLVM tidak
    • Karena sintaks ini ada di kode lama, highlighter yang baik harus mendukungnya
  • GNU assembler mengizinkan identifier yang dikutip sehingga hampir semua karakter dapat dimasukkan ke dalam simbol
  • Assembly sering digunakan bersama preprocessor C atau m4
    • Baris yang dimulai dengan dnl, m4_dnl, atau C juga harus diperlakukan sebagai komentar

Ada dan BASIC: kasus sintaks kecil yang mengguncang leksing

  • Ada cukup sederhana untuk dileksing, tetapi penggunaan tanda kutip tunggalnya unik
    • Seperti C, ia dapat memiliki literal karakter 'x'
    • Tanda kutip tunggal juga digunakan saat merujuk atribut seperti Foo'Size
    • Character'(')')'Image mendeklarasikan karakter dan mengubahnya menjadi representasi string melalui fungsi Image
  • Contoh Commodore BASIC meruntuhkan banyak asumsi syntax highlighting
    • String dapat menghilangkan tanda kutip penutup di akhir baris
    • Nama variabel diberi sigil seperti $
    • Keyword seperti goto secara aktif dileksing bahkan di dalam identifier
  • Visual BASIC memiliki sintaks literal tanggal seperti #1/1/2024#
  • Visual BASIC juga memiliki preprocessor directive seperti #If DEBUG Then, #Else, dan #End If, sehingga leksing menjadi rumit

Perl: kompleksitas di antara Shell dan bahasa pemrograman

  • Perl berada di antara Shell dan bahasa pemrograman, sehingga mewarisi kompleksitas keduanya
  • Perl menjadikan regular expression sebagai elemen first-class dalam bahasa, dan pengaruhnya berlanjut ke bahasa lain seperti Python
  • Sintaks substitusi Perl mirip sed, seperti s/hello/Perl/i
    • Karakter tanda baca arbitrer dapat digunakan sebagai delimiter sebagai pengganti /
    • Ini nyaman ketika slash masuk ke dalam regular expression, seperti s!hello!Perl!i
    • Jika menggunakan karakter berpasangan seperti s{hello}{Perl}i, karakter tambahan diperlukan
  • Perl memiliki banyak prefiks ajaib yang harus di-highlight seperti string
    • m, s, y, qr, qw, qq, qx, dan lainnya digunakan bersama berbagai delimiter
  • Agar tidak salah menilai y/x/y/ sebagai pembagian, konteks harus dilihat
    • Variabel Perl memiliki sigil seperti skalar $, array @, dan hash %, sehingga membantu membedakan tanpa harus mem-parse seluruh grammar
  • Perl memiliki kebiasaan memasukkan dokumen POD untuk man page di dalam source code
    • =word di awal baris memulai dokumen POD dan =cut mengakhirinya

Ruby: target leksing tersulit

  • Ruby terlihat seperti gabungan dari bahasa-bahasa sebelumnya, dan sintaksnya dinilai tidak terdokumentasi cukup formal
  • Dokumen syntax pada manual Ruby dinilai ringan dalam detail
  • Ruby mendukung sintaks backquote sekaligus dapat menggunakan backquote seperti nama metode, sehingga highlighter sulit menentukan apakah itu string atau bukan
  • Ruby memiliki operator << dan heredoc sekaligus
    • Bentuk seperti options[:includes] <<arg; true ada di kode nyata
    • Kode ini dapat terlihat seperti heredoc, dan Emacs pun menanganinya dengan salah
  • Kode yang memasukkan heredoc ke dalam interpolasi string seperti puts "This is #{<<HERE.strip} evil" juga valid
  • Dari 42 bahasa, Ruby disebut sebagai kejutan terbesar; Ruby mungkin tidak dapat dileksing tanpa parsing, dan bahkan dengan parsing pun sulit memastikan apakah ia dapat diinterpretasikan

Kompleksitas dilihat dari jumlah baris kode implementasi

  • Jika berdasarkan jumlah baris kode implementasi highlighter tiap bahasa, FORTH adalah yang paling sederhana dan Ruby paling kompleks
  • Implementasi terpendek adalah highlight_forth.cpp dengan 125 baris
  • Implementasi yang relatif pendek mencakup m4 132 baris, Ada 149 baris, LISP 160 baris, MATLAB 166 baris, COBOL 186 baris, BASIC 199 baris, FORTRAN 200 baris, dan lainnya
  • Implementasi skala menengah mencakup JavaScript 337 baris, TypeScript 371 baris, Kotlin 387 baris, Scala 387 baris, Assembly 447 baris, C 449 baris, Swift 455 baris, dan D 521 baris
  • Implementasi terpanjang adalah Shell 570 baris, Perl 583 baris, dan highlight_ruby.cpp 1042 baris

1 komentar

 
GN⁺ 2024-11-03
Komentar Hacker News
  • Dari semua trigraph C, yang paling saya suka adalah bentuk seperti do_action() ??!??! handle_error()
    Sekilas terlihat seperti sintaks penanganan error khusus, tetapi ternyata ??!??! diubah menjadi OR logis ||, dan dengan aturan short-circuit evaluation, strukturnya menjalankan handle_error() saat do_action() mengembalikan nilai non-nol, jadi terasa memuaskan

  • Bacaan yang menarik, tetapi justru membuat saya lebih bersimpati pada sudut pandang Lisp
    Sejauh yang saya pahami, posisinya adalah bahwa sintaks bukan bagian yang terlalu penting dari sebuah bahasa, dan karena cenderung lebih menjadi hambatan daripada bantuan, sintaks sebaiknya dibuat sesederhana dan seseragam mungkin agar kita bisa fokus pada hal lain
    Namun, belajar structural editing di Lisp sejauh ini lebih terasa seperti hambatan daripada bantuan, dan saya berharap suatu saat ada imbalannya

    • Sintaks sederhana mungkin baik untuk komputer, tetapi sintaks terutama dirancang untuk dibaca dan ditulis manusia
      Jika dibuat sesederhana Lisp, perdebatan sintaks hanya terdorong menjadi masalah semantik, jadi lapisannya saja yang berubah
      Menurut saya sintaks yang kompleks jauh lebih mudah dibaca dan ditulis daripada sintaks sederhana dengan semantik yang kompleks. Error sintaks mendapat umpan balik cepat, sedangkan error semantik bisa tersembunyi sampai runtime
    • Lisp memiliki reader macro yang dapat memprogram ulang lexer, dan proses mengubah struktur yang terlihat menjadi syntax tree juga dapat ditangani dengan macro
      Contohnya ada https://pyret.org/. Dalam praktiknya, sulit mengatakan Lisp itu sederhana atau pasti seragam
    • Saya agak terkejut structural editing menjadi hambatan; sebagai saran, cukup gunakan parinfer dan ketahui key binding untuk tiga perintah slurp, barf, dan raise
      Dengan empat hal ini saja, Anda bisa mendapatkan sekitar 95% manfaat paredit tanpa kompleksitasnya, dan trik lainnya bisa dipelajari setelah terbiasa
  • Ada bagian yang mengatakan “saya tidak tahu siapa yang ingin melakukan syntax highlighting C pada 35MB per detik, tetapi sekarang itu mungkin”; memang cepat, tetapi tcc bahkan di komputer yang sangat tua dapat mengompilasi 29MB C per detik menjadi kode biner: https://bellard.org/tcc/#speed
    Mungkin bisa dibuat lebih cepat lagi, tetapi tampaknya tidak terlalu perlu

  • Ada beberapa hal yang terlewat oleh penulis
    Bukan hanya TypeScript, Swift, Kotlin, dan Scala yang sampai pada ekstrem memasukkan kode sungguhan ke dalam interpolasi string; C#, Python, JavaScript, Ruby, Shell, dan Make juga mendukung hal serupa
    Di Tcl, kode dan data sulit dibedakan sehingga { } pada dasarnya adalah delimiter string yang unik, dan pada xyzzy {#hello world}, apakah #hello world itu komentar atau string tidak dapat diketahui sebelum waktu evaluasi
    PostgreSQL memiliki string ber-quote dolar yang praktis, sehingga seperti di https://www.postgresql.org/docs/current/sql-syntax-lexical.h..., 'Dianne''s horse', $$Dianne's horse$$, dan $SomeTag$Dianne's horse$SomeTag$ memiliki makna yang sama

    • Perl juga bisa
      String seperti "I have $foo $bar's: @{[$bar x $foo]}" dapat mencetak I have 5 x's: xxxxx
      Sintaks @{[...]} memanfaatkan karakteristik Perl yang memungkinkan interpolasi array selain skalar; [...] di dalam membuat referensi array, lalu @{...} di luar melakukan dereference terhadapnya. Interpreter Perl mengizinkan kode arbitrer dalam ekspresi bagian dalam itu
    • Menurut saya, ini lebih tepat dilihat sebagai sintaks penggabungan string khusus daripada “kode masuk ke dalam string”
      Dengan begitu, ia bisa dinesting seperti "foo { toUpper("bar { x + y } bar") } foo", dan jika + adalah penggabungan string, pada dasarnya sama dengan "foo " + toUpper("bar " + (x + y) + " bar") + " foo"
      Saya tidak tahu apakah ada bahasa yang benar-benar bekerja seperti ini
    • Ruby mendorong hal ini sampai ekstrem
      Saya suka Ruby, tetapi Ruby valid seperti puts "This is #{< yang mencampur interpolasi string dan heredoc sulit dibela
      Selain itu, di Ruby spasi juga bisa menjadi karakter quote. Dalam konteks tanpa operand kiri, % memulai string ber-quote dan karakter berikutnya menunjukkan jenis quote, sehingga % hello menjadi string yang berisi hello. %(this is a string) atau %{this is a string} bagus, tetapi saya belum pernah melihat penggunaan spasi di alam liar, dan irb juga tidak menanganinya dengan benar, jadi rasanya boleh saja dihapus
    • Interpolasi string Scala juga dapat digunakan sebagai target pattern matching
      Jika menulis val s"${a} + ${b}" = "1 + 2", maka a menjadi 1 dan b menjadi 2
    • Hal keren dari interpolated string C# adalah bahwa ia dievaluasi secara lazy
      Dulu, dalam hot loop, kode seperti log.trace($"Entering iteration {i} for customer {c.ID} [{c.ShortName}]"); memanggil string.Concat setiap kali bahkan sebelum logger keluar, sehingga banyak logger mengimplementasikan interpolasi sendiri
      Di C#, Anda dapat mendeklarasikan overload yang menerima DefaultInterpolatedStringHandler atau pola handler kustom, dan overload ini diprioritaskan sehingga dapat memeriksa apakah log benar-benar diperlukan sebelum menunda pembuatan string
  • Ada satu lagi keanehan sintaksis yang tidak disebutkan di sini, yang merusak sebagian besar syntax highlighter
    Di Java, Unicode escape bisa muncul di mana saja, bukan hanya di dalam string
    Misalnya class Foo\u007b} adalah kelas yang valid, dan \u000A di dalam komentar // bisa diproses sebagai newline sungguhan, sehingga contoh assert juga berperilaku tidak seperti yang diperkirakan

    • Saya juga menganggap ketidakmampuan melakukan syntax highlighting dengan benar untuk hal ini sebagai masalah keamanan
      Karena komentar blok bisa diakhiri dengan Unicode escape, untuk menyembunyikan kode berbahaya di dalam komentar pada file sumber Java, cukup buat alasan yang masuk akal mengapa ada blok Unicode escape di dalam komentar
      Developer yang tidak tahu sifat ini kemungkinan besar akan melihatnya sebagai kode yang dikomentari dan melewatkannya begitu saja
    • Saya belum pernah melihat hal seperti ini di Java; apakah ada kegunaan praktisnya?
  • Ada bagian yang mengatakan “Ruby adalah bahasa yang menghindari semua upaya memahami sintaksnya”, tetapi dari sudut pandang TeX yang punya lexer yang bisa diprogram ulang secara arbitrer, itu masih tergolong imut

    • Macro reader Lisp juga bisa memprogram lexer
  • Katanya “semua programmer C tahu bahwa komentar multi-baris tidak bisa dimasukkan ke dalam komentar multi-baris”, tetapi bagi programmer Standard ML, itu bisa terasa seperti batasan yang mengejutkan
    Komentar bersarang seperti (* (* Nested (**) *) comment *) valid, dan val _ = print "hello, world\n" setelahnya juga berjalan normal
    Mengingat saat C muncul ia dianggap sebagai bahasa yang ekspresif, menarik bahwa komentar bersarang tidak dimasukkan ke dalam bahasa tersebut

    • Ada tiga hal menarik dari pernyataan itu
      ML tidak punya komentar satu baris, jadi itu juga batasan yang sama mengejutkannya; dan ini pertama kalinya saya mendengar C disebut “ekspresif”, meski pada 1972 dibandingkan assembly mungkin memang begitu
      Selain itu, saya tidak tahu apa hubungan sintaks komentar dengan ekspresivitas bahasa, dan menurut definisi saya rasa tidak ada sama sekali
    • Untuk melakukan lexing komentar bersarang, diperlukan stack atau setidaknya penghitung tingkat nesting
      Secara tradisional, analisis leksikal dianggap sebagai ranah yang hanya memakai finite-state automaton seperti regex, sehingga fitur seperti itu terlihat berada di luar cakupan lexing
    • Di C juga ada satu cara untuk membuat komentar bersarang dengan memakai #if 0
      Bisa dibungkus seperti #if 0 ... #if 0 ... #endif ... #endif
    • Pascal juga sejak dulu mendukung sintaks komentar bersarang seperti pada contoh
    • Ini berlaku bukan hanya untuk Standard ML, tetapi juga ML secara umum
  • Saya ingin melihat implementasi syntax highlighting joe dalam bentuk yang bisa digunakan ulang
    Format di https://joe-editor.sf.net/ cukup kuat untuk menyorot Python f-string dengan benar
    Dokumentasi dan contoh terkait ada di https://github.com/cmur2/joe-syntax/blob/joe-4.4/misc/HowItW..., https://gist.github.com/irdc/6188f11b1e699d615ce2520f03f1d0d...

    • Saya pernah membuat beberapa lexer dan parser berdasarkan pendekatan parsing DFA milik joe
      Sintaks state dan transisinya jauh lebih mudah dipahami dibandingkan tool standar
      Kekurangannya adalah kumpulan aturan menjadi bertele-tele dan agak sulit disusun secara ideal, tetapi menurut saya justru menjadi kelebihan karena semua aturan produksi lebih mudah disimpulkan dari kode
    • Menariknya, karena sintaks Python f-string berubah di 3.12, highlighting-nya juga harus berbeda tergantung versi
  • Sepertinya penulis belum pernah mencoba syntax highlighting untuk TeX
    Itu mungkin baik untuk kesehatan mental, karena di TeX biasanya syntax highlighting yang lengkap mustahil dilakukan tanpa interpretasi
    Parsing saja bahkan tidak cukup, karena peran tiap karakter bisa didefinisikan ulang, sehingga hal seperti “mulai sekarang K adalah { dan C adalah }” pun bisa dilakukan
    Dalam kenyataannya, ada juga paper arXiv yang memakai fitur terkutuk ini

    • Saya membuat https://github.com/Mozilla-Ocho/llamafile/blob/main/llamafil..., dan untuk file .tex yang saya temukan di hard drive, highlighting-nya cukup bagus tanpa rusak
      Targetnya adalah mencakup 99,9% penggunaan dunia nyata. Dengan begitu, kemungkinan besar keluaran LLM pun sebagian besar tercakup
      Sintaks yang sulit dipahami biasanya juga bukan masalah besar selama string atau komentar tidak berlanjut sampai akhir file dan menutupi sisa source code
    • Saya sulit percaya ketika tahu bahwa \makeatletter bukan berarti “membuat sesuatu menjadi karakter at”, melainkan berarti memperlakukan karakter @ sebagai huruf saat parsing
    • Common Lisp juga bisa mendefinisikan ulang readtable dengan cara serupa. Hanya saja kemungkinan lebih kecil disalahgunakan di arXiv
  • Menurut saya, menulis engine pewarnaan sintaks yang bagus seperti Vim itu tidak mudah
    Pewarnaan sintaks membutuhkan pemrosesan konteks, yaitu menerapkan aturan yang berbeda pada konten yang bertumpuk dengan cara tertentu
    Penyorot sintaks Vim memungkinkan kita mendeklarasikan dua jenis item, match dan region; match adalah aturan leksikal sederhana, sedangkan region memiliki ekspresi yang masing-masing mencocokkan awal, akhir, dan bagian tengah
    Item bisa dideklarasikan agar hanya aktif ketika berada di dalam wilayah tertentu, dan relasi penyertaan juga bisa saling dideklarasikan
    Di atas struktur makna dasar ini, ada berbagai fitur yang ditambahkan untuk situasi khusus. Bahkan Justine pun rasanya sulit membuatnya langsung di sesi wawancara; mungkin perlu setidaknya tugas semalaman

    • Contoh yang sulit ditangani adalah TXR Lisp yang tertanam di dalam bahasa TXR
      Di sini ada skrip genman yang menerima hasil konversi HTML manpage lalu memolesnya menjadi HTML manual TXR: https://www.kylheku.com/cgit/txr/tree/genman.txr
      Bagian yang terlihat putih adalah template literal, sementara kode Lisp berada di dalam direktif seperti @(do ...). Kata kunci TXR tampak ungu, kata kunci TXR Lisp tampak hijau, dan kata yang sama pun bisa berbeda tergantung konteks
      Quasi-string juga bisa memuat tata bahasa bertumpuk, dan di dalamnya bisa ada lagi kode tertanam atau quasi-string. File definisi sintaks txr.vim dan tl.vim keduanya dihasilkan dari https://www.kylheku.com/cgit/txr/tree/genvim.txr
    • Secara naif, saya sempat berpikir bahwa untuk membuat penyorot sintaks dengan benar, cukup mem-parse menjadi AST lalu menelusuri token sambil memperbarui warna berdasarkan tipe node
      Kalau tidak dilakukan begitu, mungkin karena masalah efisiensi. Misalnya, alih-alih hanya menyorot bagian yang terlihat di layar, harus mem-parse seluruh file sehingga biayanya bisa membesar
    • Saya tidak akan bertaruh bahwa Justine tidak bisa membuatnya dalam wawancara ;)