Shrubbery syntax adalah fondasi notasi struktural yang digunakan oleh ekosistem Racket modern (termasuk dialek Rhombus) untuk menjembatani keterbacaan sintaksis berbasis indentasi dengan fleksibilitas manipulasi pohon sintaks abstrak (AST) khas S-expression. Ketika tim menggunakan DSL (Domain-Specific Language) berbasis Shrubbery, inkonsistensi spasi atau penempatan operator dapat merusak struktur pohon token sebelum fase kompilasi semantik dimulai. Mengintegrasikan validasi parsing Shrubbery syntax di CI (Continuous Integration) mendeteksi kesalahan sintaksis lebih awal dan mencegah masuknya format rusak ke repositori utama.

Arsitektur Shrubbery Syntax: Tokenisasi, Indentasi, dan Grup

Berbeda dari S-expression tradisional yang hanya bergantung pada tanda kurung (...), parser Shrubbery membagi kode ke dalam tingkatan struktural berbasis indentasi, operator, dan pembatas. Shrubbery tidak langsung menghasilkan representasi semantik, melainkan menghasilkan struktur intermediat bernama shrubbery term.

1. Indentasi dan Baris (Lines and Groups)

Shrubbery mengubah baris bertingkat menjadi struktur grup (group) dan blok (block). Setiap blok diindikasikan oleh baris baru yang memiliki indentasi lebih dalam setelah tanda titik dua (:) atau operator pembuka. Jika indentasi tidak konsisten (misalnya pencampuran tab dan spasi atau level indentasi yang ganjil), tokeniser shrubbery/parse langsung melemparkan error leksikal tanpa melanjutkan ke tahap kompilasi kode Racket.

2. Operator Precedence pada Tingkat Sintaksis

Di S-expression, urutan evaluasi ditentukan sepenuhnya oleh tanda kurung: (+ (* a b) c). Shrubbery memungkinkan notasi infix tanpa membubarkan sifat Lisp-nya. Shrubbery mengelompokkan token berdampingan dan membiarkan macro layer atau resolver tata bahasa menentukan preseden operator berurutan di dalam grup yang sama, asalkan delimitasi grupnya valid.

Script Validasi Syntax Menggunakan Racket

Untuk memvalidasi file DSL berbasis Shrubbery secara programmatic, manfaatkan package shrubbery yang disediakan oleh Racket. Script ini membaca berkas target dan mem-parsing stream menjadi struktur shrubbery. Script mengembalikan exit code 0 jika valid, atau 1 jika terjadi kegagalan parse.

#lang racket/base

(require racket/cmdline
         racket/file
         shrubbery/parse)

(define (validate-shrubbery file-path)
  (with-handlers
    ([exn:fail:read?
      (λ (e)
        (eprintf "[ERROR] Syntax error in ~a: ~a\n" file-path (exn-message e))
        #f)]
     [exn:fail?
      (λ (e)
        (eprintf "[ERROR] Unexpected error reading ~a: ~a\n" file-path (exn-message e))
        #f)])
    (call-with-input-file file-path
      (λ (in)
        (port-count-lines! in)
        ;; parse-all membaca seluruh file menjadi list of shrubbery groups
        (parse-all in)
        #t))))

(define (main)
  (define files (vector->list (current-command-line-arguments)))
  (when (null? files)
    (eprintf "Usage: racket validate-shrubbery.rkt <file1.rktl> [...files]\n")
    (exit 1))

  (define failed? #f)
  (for ([file files])
    (unless (file-exists? file)
      (eprintf "[WARN] File not found: ~a\n" file)
      (set! failed? #t))
    (when (file-exists? file)
      (define ok? (validate-shrubbery file))
      (unless ok?
        (set! failed? #t))))

  (if failed?
      (exit 1)
      (begin
        (displayln "All Shrubbery DSL files parsed successfully.")
        (exit 0))))

(main)

Implementasi Pre-commit Hook

Pemeriksaan lokal sebelum kode didorong (pushed) menghemat waktu antrean CI. Buat berkas executable pada .git/hooks/pre-commit atau gunakan framework pengelola hook seperti Husky / pre-commit.

#!/usr/bin/env bash
set -euo pipefail

# Ambil berkas DSL berbasis shrubbery yang ada di stage Git
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(rhm|shrub)$' || true)

if [ -z "$FILES" ]; then
  exit 0
fi

echo "Running Shrubbery syntax validation..."
racket scripts/validate-shrubbery.rkt $FILES

Pastikan script hook memiliki hak akses eksekusi menggunakan perintah chmod +x .git/hooks/pre-commit.

Workflow CI Menggunakan GitHub Actions

Langkah validasi di GitHub Actions memastikan tidak ada commit cacat yang lolos dari mesin lokal pengembang. Gunakan runner berbasis Linux dengan instalasi minimal Racket.

name: Validate Shrubbery Syntax

on:
  pull_request:
    paths:
      - '**/*.rhm'
      - '**/*.shrub'
      - 'scripts/**'
  push:
    branches:
      - main

jobs:
  validate-dsl:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Racket
        uses: Bogdanp/[email protected]
        with:
          architecture: 'x64'
          distribution: 'minimal'
          version: '8.12'

      - name: Install Shrubbery Package
        run: |
          raco pkg install --auto --batch shrubbery

      - name: Validate DSL Files
        run: |
          # Temukan semua file DSL target dan jalankan validator
          mapfile -t FILES < <(find . -type f \( -name "*.rhm" -o -name "*.shrub" \))
          if [ ${#FILES[@]} -gt 0 ]; then
            racket scripts/validate-shrubbery.rkt "${FILES[@]}"
          else
            echo "No Shrubbery files found to validate."
          fi

Kesalahan Umum dan Troubleshooting

  • Pencampuran Spasi dan Tab: Shrubbery menolak berkas yang mencampurkan karakter tab dan spasi untuk indentasi dalam scope yang sama. Standardisasi editor config ke 2 atau 4 spasi mutlak diperlukan.
  • Line Counting Nonaktif: Pemanggilan port-count-lines! wajib dipanggil sebelum parse-all dieksekusi pada input port. Jika dilewati, pesan error parser tidak akan menyertakan baris dan kolom yang presisi saat terjadi kesalahan sintaksis.
  • File Encoding: Parser Shrubbery mengharapkan representasi UTF-8 valid. Berkas dengan byte-order mark (BOM) tertentu dapat memicu exn:fail:read di awal pembacaan stream.