1. Pendahuluan

Expr adalah sebuah solusi konfigurasi dinamis yang dirancang untuk bahasa Go, dikenal karena sintaksis sederhananya dan fitur kinerja yang kuat. Inti dari mesin ekspresi Expr difokuskan pada keamanan, kecepatan, dan intuitivitas, menjadikannya cocok untuk skenario seperti pengendalian akses, penyaringan data, dan pengelolaan sumber daya. Ketika diterapkan pada Go, Expr sangat meningkatkan kemampuan aplikasi untuk menangani aturan-aturan dinamis. Berbeda dengan penerjemah atau mesin skrip di bahasa lain, Expr mengadopsi pemeriksaan tipe statis dan menghasilkan bytecode untuk eksekusi, memastikan kinerja dan keamanan.

2. Instalasi Expr

Anda dapat menginstal mesin ekspresi Expr menggunakan alat manajemen paket bahasa Go, yaitu go get:

go get github.com/expr-lang/expr

Perintah ini akan mengunduh file-file pustaka Expr dan menginstalnya ke dalam proyek Go Anda, memungkinkan Anda untuk mengimpor dan menggunakan Expr dalam kode Go Anda.

3. Memulai dengan Cepat

3.1 Compile dan Jalankan Ekspresi Dasar

Mari mulai dengan contoh dasar: menulis ekspresi sederhana, mengompilasinya, dan kemudian menjalankannya untuk mendapatkan hasilnya.

package main

import (
	"fmt"
	"github.com/expr-lang/expr"
)

func main() {
	// Mengompilasi ekspresi penjumlahan dasar
	program, err := expr.Compile(`2 + 2`)
	if err != nil {
		panic(err)
	}

	// Menjalankan ekspresi yang telah dikompilasi tanpa melewati lingkungan, karena tidak ada variabel yang diperlukan di sini
	output, err := expr.Run(program, nil)
	if err != nil {
		panic(err)
	}

	// Mencetak hasil
	fmt.Println(output)  // Menghasilkan 4
}

Pada contoh ini, ekspresi 2 + 2 dikompilasi menjadi bytecode yang dapat dieksekusi, yang kemudian dijalankan untuk menghasilkan output.

3.2 Menggunakan Ekspresi Variabel

Selanjutnya, kita akan membuat sebuah lingkungan yang berisi variabel, menulis sebuah ekspresi yang menggunakan variabel-variabel tersebut, mengompilasi dan menjalankan ekspresi tersebut.

package main

import (
	"fmt"
	"github.com/expr-lang/expr"
)

func main() {
	// Membuat lingkungan dengan variabel-variabel
	env := map[string]interface{}{
		"foo": 100,
		"bar": 200,
	}

	// Mengompilasi ekspresi yang menggunakan variabel dari lingkungan
	program, err := expr.Compile(`foo + bar`, expr.Env(env))
	if err != nil {
		panic(err)
	}

	// Menjalankan ekspresi
	output, err := expr.Run(program, env)
	if err != nil {
		panic(err)
	}

	// Mencetak hasil
	fmt.Println(output)  // Menghasilkan 300
}

Pada contoh ini, lingkungan env berisi variabel foo dan bar. Ekspresi foo + bar menerka tipe dari foo dan bar dari lingkungan selama kompilasi, dan menggunakan nilai-nilai dari variabel ini saat runtime untuk mengevaluasi hasil ekspresi.

4. Detail Syntax Expr

4.1 Variabel dan Literal

Mesin ekspresi Expr dapat menangani literal tipe data umum, termasuk angka, string, dan nilai boolean. Literal adalah nilai data yang ditulis langsung dalam kode, seperti 42, "hello", dan true.

Angka

Di Expr, Anda dapat langsung menulis bilangan bulat dan bilangan desimal:

42      // Mewakili bilangan bulat 42
3.14    // Mewakili bilangan desimal 3.14

String

Literal string diapit oleh tanda kutip ganda " atau backticks ``. Sebagai contoh:

"hello, dunia" // String diapit oleh tanda kutip ganda, mendukung karakter escape
`hello, dunia` // String diapit oleh backticks, mempertahankan format string tanpa mendukung karakter escape

Boolean

Hanya ada dua nilai boolean, true dan false, mewakili benar dan salah logis:

true   // Nilai boolean true
false  // Nilai boolean false

Variabel

Expr juga memungkinkan definisi variabel dalam lingkungan, dan kemudian merujuk variabel-variabel ini dalam ekspresi. Sebagai contoh:

env := map[string]interface{}{
    "umur": 25,
    "nama": "Alice",
}

Lalu dalam ekspresi, Anda dapat merujuk ke umur dan nama:

umur > 18  // Memeriksa apakah umur lebih dari 18
nama == "Alice"  // Menentukan apakah nama sama dengan "Alice"

4.2 Operator

Mesin ekspresi Expr mendukung berbagai operator, termasuk operator aritmatika, operator logika, operator perbandingan, dan operator set, dll.

Operator Aritmatika dan Logika

Operator aritmatika meliputi penambahan (+), pengurangan (-), perkalian (*), pembagian (/), dan modulo (%). Operator logika meliputi logika AND (&&), logika OR (||), dan logika NOT (!), misalnya:

2 + 2 // Hasilnya adalah 4
7 % 3 // Hasilnya adalah 1
!true // Hasilnya adalah false
umur >= 18 && nama == "Alice" // Periksa apakah umur tidak kurang dari 18 dan apakah nama sama dengan "Alice"

Operator Pembanding

Operator pembanding meliputi sama dengan (==), tidak sama dengan (!=), kurang dari (<), kurang dari atau sama dengan (<=), lebih besar dari (>), dan lebih besar dari atau sama dengan (>=), digunakan untuk membandingkan dua nilai:

umur == 25 // Periksa apakah umur sama dengan 25
umur != 18 // Periksa apakah umur tidak sama dengan 18
umur > 20  // Periksa apakah umur lebih besar dari 20

Operator Set

Expr juga menyediakan beberapa operator untuk bekerja dengan set, seperti in untuk memeriksa apakah sebuah elemen ada dalam set. Set dapat berupa array, slice, atau peta:

"user" in ["user", "admin"]  // true, karena "user" ada di dalam array
3 in {1: true, 2: false}     // false, karena 3 bukan kunci dalam peta

Terdapat juga beberapa fungsi operasi set lanjutan, seperti all, any, one, dan none, yang memerlukan penggunaan fungsi anonim (lambda):

all(tweets, {.Len <= 240})  // Periksa apakah bidang Len dari semua tweets tidak melebihi 240
any(tweets, {.Len > 200})   // Periksa apakah ada sebuah bidang Len dalam tweets yang melebihi 200

Operator Anggota

Dalam bahasa ekspresi Expr, operator anggota memungkinkan kita untuk mengakses properti dari struct dalam bahasa Go. Fitur ini memungkinkan Expr untuk langsung memanipulasi struktur data kompleks, sehingga sangat fleksibel dan praktis.

Menggunakan operator anggota sangatlah sederhana, hanya menggunakan operator . diikuti dengan nama properti. Misalnya, jika kita memiliki struct berikut:

type User struct {
    Nama string
    Umur int
}

Anda dapat menulis sebuah ekspresi untuk mengakses properti Nama dari struktur User seperti ini:

env := map[string]interface{}{
    "user": User{Nama: "Alice", Umur: 25},
}

code := `user.Nama`

program, err := expr.Compile(code, expr.Env(env))
if err != nil {
    panic(err)
}

output, err := expr.Run(program, env)
if err != nil {
    panic(err)
}

fmt.Println(output) // Output: Alice

Menangani Nilai-nilai NULL

Ketika mengakses properti, Anda mungkin menghadapi situasi di mana objek adalah nil. Expr menyediakan akses properti yang aman, sehingga bahkan jika struktur atau properti bersarang adalah nil, itu tidak akan menyebabkan kesalahan panic pada saat runtime.

Gunakan operator ?. untuk merujuk kepada properti. Jika objek adalah nil, akan mengembalikan nil daripada melemparkan kesalahan.

pengarang.Pengguna?.Nama

Ekspresi yang setara

pengarang.Pengguna != nil ? pengarang.Pengguna.Nama : nil

Penggunaan operator ?? utamanya adalah untuk mengembalikan nilai default:

pengarang.Pengguna?.Nama ?? "Anonymous"

Ekspresi yang setara

pengarang.Pengguna != nil ? pengarang.Pengguna.Nama : "Anonymous"

Operator Pipa

Operator pipa (|) dalam Expr digunakan untuk meneruskan hasil dari satu ekspresi sebagai parameter ke ekspresi lainnya. Hal ini mirip dengan operasi pipa dalam shell Unix, memungkinkan beberapa modul fungsional dihubungkan bersama untuk membentuk pipa pemrosesan. Dalam Expr, penggunaan operator ini dapat membuat ekspresi lebih jelas dan ringkas.

Sebagai contoh, jika kita memiliki fungsi untuk mendapatkan nama pengguna dan sebuah template untuk pesan selamat datang:

env := map[string]interface{}{
    "user":      User{Name: "Bob", Age: 30},
    "get_name":  func(u User) string { return u.Name },
    "greet_msg": "Hello, %s!",
}

code := `get_name(user) | sprintf(greet_msg)`

program, err := expr.Compile(code, expr.Env(env))
if err != nil {
    panic(err)
}

output, err := expr.Run(program, env)
if err != nil {
    panic(err)
}

fmt.Println(output) // Output: Hello, Bob!

Pada contoh ini, kita pertama-tama mendapatkan nama pengguna melalui get_name(user), kemudian meneruskan nama tersebut ke fungsi sprintf menggunakan operator pipa | untuk menghasilkan pesan selamat datang akhir.

Penggunaan operator pipa dapat memodulisasi kode kita, meningkatkan reuseabilitas kode, dan membuat ekspresi lebih mudah dibaca.

4.3 Fungsi

Expr mendukung fungsi bawaan dan fungsi kustom, membuat ekspresi lebih kuat dan fleksibel.

Menggunakan Fungsi Bawaan

Fungsi bawaan seperti len, all, none, any, dll. dapat digunakan langsung dalam ekspresi.

// Contoh penggunaan fungsi bawaan
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
    panic(err)
}

// Catatan: di sini env harus berisi variabel pengguna, dan setiap pengguna harus memiliki properti Age
output, err := expr.Run(program, env)
fmt.Print(output) // Jika semua pengguna di env berusia 18 tahun atau lebih, akan mengembalikan true

Cara Mendefinisikan dan Menggunakan Fungsi Kustom

Di Expr, Anda dapat membuat fungsi kustom dengan memasukkan definisi fungsi dalam pemetaan lingkungan.

// Contoh fungsi kustom
env := map[string]interface{}{
    "greet": func(name string) string {
        return fmt.Sprintf("Hello, %s!", name)
    },
}

program, err := expr.Compile(`greet("World")`, expr.Env(env))
if err != nil {
    panic(err)
}

output, err := expr.Run(program, env)
fmt.Print(output) // Mengembalikan Hello, World!

Ketika menggunakan fungsi dalam Expr, Anda dapat memodulisasi kode Anda dan menggabungkan logika kompleks ke dalam ekspresi. Dengan menggabungkan variabel, operator, dan fungsi, Expr menjadi alat yang kuat dan mudah digunakan. Selalu pastikan keamanan tipe saat membangun lingkungan Expr dan menjalankan ekspresi.

5. Dokumentasi Fungsi Bawaan

Mesin ekspresi Expr menyediakan pengembang dengan rangkaian fungsi bawaan yang kaya untuk menangani berbagai skenario kompleks. Di bawah ini, kami akan rincikan fungsi bawaan ini dan penggunaannya.

all

Fungsi all dapat digunakan untuk memeriksa apakah semua elemen dalam sebuah koleksi memenuhi kondisi tertentu. Fungsi ini mengambil dua parameter: koleksi dan ekspresi kondisi.

// Memeriksa apakah semua twit memiliki panjang konten kurang dari 240
code := `all(tweets, len(.Content) < 240)`

any

Mirip dengan all, fungsi any digunakan untuk memeriksa apakah ada elemen dalam sebuah koleksi yang memenuhi kondisi.

// Memeriksa apakah ada twit dengan panjang konten lebih dari 240
code := `any(tweets, len(.Content) > 240)`

none

Fungsi none digunakan untuk memeriksa apakah tidak ada elemen dalam sebuah koleksi yang memenuhi kondisi.

// Memastikan tidak ada twit yang diulang
code := `none(tweets, .IsRepeated)`

one

Fungsi one digunakan untuk memastikan bahwa hanya satu elemen dalam sebuah koleksi memenuhi kondisi.

// Memeriksa apakah hanya satu twit mengandung kata kunci tertentu
code := `one(tweets, contains(.Content, "keyword"))`

filter

Fungsi filter dapat menyaring elemen koleksi yang memenuhi kondisi tertentu.

// Menyaring semua twit yang ditandai sebagai prioritas
code := `filter(tweets, .IsPriority)`

map

Fungsi map digunakan untuk mentransformasi elemen dalam sebuah koleksi sesuai dengan metode yang ditentukan.

// Memformat waktu terbit semua twit
code := `map(tweets, {.PublishTime: Format(.Date)})`

len

Fungsi len digunakan untuk mengembalikan panjang dari sebuah rangkaian data atau string.

// Mendapatkan panjang dari username
code := `len(username)`

contains

Fungsi contains digunakan untuk memeriksa apakah sebuah string mengandung sub-string tertentu atau apakah sebuah kumpulan data mengandung elemen tertentu.

// Memeriksa apakah username mengandung karakter-karakter ilegal
code := `contains(username, "karakter ilegal")`

Yang disebutkan di atas hanyalah sebagian dari fungsi bawaan yang disediakan oleh mesin ekspresi Expr. Dengan fungsi-fungsi yang kuat ini, Anda dapat menangani data dan logika dengan lebih fleksibel dan efisien. Untuk daftar fungsi yang lebih lengkap dan petunjuk penggunaan, silakan lihat dokumentasi resmi Expr.