1. Einführung

Expr ist eine dynamische Konfigurationslösung, die für die Go-Sprache entwickelt wurde und für ihre einfache Syntax und leistungsstarken Leistungsmerkmale bekannt ist. Der Kern des Expr-Ausdrucks-Engines konzentriert sich auf Sicherheit, Geschwindigkeit und Anschaulichkeit und ist somit für Szenarien wie Zugriffskontrolle, Datenfilterung und Ressourcenverwaltung geeignet. Wenn Expr in Go angewendet wird, verbessert es erheblich die Fähigkeit von Anwendungen, dynamische Regeln zu behandeln. Im Gegensatz zu Interpretern oder Skript-Engines in anderen Sprachen verwendet Expr eine statische Typüberprüfung und generiert Bytecode für die Ausführung, um sowohl Leistung als auch Sicherheit zu gewährleisten.

2. Expr installieren

Sie können den Expr-Ausdrucks-Engine mithilfe des Paketverwaltungstools go get der Go-Sprache installieren:

go get github.com/expr-lang/expr

Mit diesem Befehl werden die Expr-Bibliotheksdateien heruntergeladen und in Ihr Go-Projekt installiert, sodass Sie Expr in Ihrem Go-Code importieren und verwenden können.

3. Schnellstart

3.1 Kompilieren und Ausführen einfacher Ausdrücke

Beginnen wir mit einem einfachen Beispiel: Schreiben eines einfachen Ausdrucks, Kompilieren und dann Ausführen, um das Ergebnis zu erhalten.

package main

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

func main() {
	// Kompilieren eines einfachen Additionsausdrucks
	program, err := expr.Compile(`2 + 2`)
	if err != nil {
		panic(err)
	}

	// Ausführen des kompilierten Ausdrucks, ohne eine Umgebung zu übergeben, da hier keine Variablen benötigt werden
	output, err := expr.Run(program, nil)
	if err != nil {
		panic(err)
	}

	// Ergebnis ausgeben
	fmt.Println(output)  // Gibt 4 aus
}

In diesem Beispiel wird der Ausdruck 2 + 2 in ausführbaren Maschinencode kompiliert, der dann ausgeführt wird, um die Ausgabe zu erzeugen.

3.2 Verwenden von Variablenausdrücken

Als nächstes erstellen wir eine Umgebung mit Variablen, schreiben einen Ausdruck, der diese Variablen verwendet, und kompilieren und führen diesen Ausdruck aus.

package main

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

func main() {
	// Erstellen einer Umgebung mit Variablen
	env := map[string]interface{}{
		"foo": 100,
		"bar": 200,
	}

	// Kompilieren eines Ausdrucks, der Variablen aus der Umgebung verwendet
	program, err := expr.Compile(`foo + bar`, expr.Env(env))
	if err != nil {
		panic(err)
	}

	// Ausführen des Ausdrucks
	output, err := expr.Run(program, env)
	if err != nil {
		panic(err)
	}

	// Ergebnis ausgeben
	fmt.Println(output)  // Gibt 300 aus
}

In diesem Beispiel enthält die Umgebung env die Variablen foo und bar. Der Ausdruck foo + bar leitet die Typen von foo und bar während der Kompilierung aus der Umgebung ab und verwendet dann zur Laufzeit die Werte dieser Variablen, um das Ergebnis des Ausdrucks zu bewerten.

4. Expr-Syntax im Detail

4.1 Variablen und Literale

Die Expr-Ausdrucks-Engine kann gebräuchliche Literaldatentypen verarbeiten, einschließlich Zahlen, Zeichenketten und boolescher Werte. Literale sind Datenwerte, die direkt im Code geschrieben sind, wie 42, "hello" und true.

Zahlen

In Expr können Sie direkt Ganzzahlen und Gleitkommazahlen schreiben:

42      // Repräsentiert die Ganzzahl 42
3.14    // Repräsentiert die Gleitkommazahl 3.14

Zeichenketten

Zeichenkettenliterale sind in doppelten Anführungszeichen " oder Backticks `` eingeschlossen. Zum Beispiel:

"hello, world" // Zeichenkette in doppelten Anführungszeichen, unterstützt Escape-Zeichen
`hello, world` // Zeichenkette in Backticks eingeschlossen, behält das Zeichenkettenformat ohne Unterstützung für Escape-Zeichen bei

Boolesche Werte

Es gibt nur zwei boolesche Werte, true und false, die logisches true und false repräsentieren:

true   // Boolescher true-Wert
false  // Boolescher falscher Wert

Variablen

Expr erlaubt auch die Definition von Variablen in der Umgebung und referenziert diese Variablen dann im Ausdruck. Zum Beispiel:

env := map[string]interface{}{
    "age": 25,
    "name": "Alice",
}

Dann im Ausdruck können Sie auf age und name verweisen:

age > 18  // Überprüfen, ob das Alter größer als 18 ist
name == "Alice"  // Feststellen, ob der Name gleich "Alice" ist

4.2 Operatoren

Die Expr-Ausdrucks-Engine unterstützt verschiedene Operatoren, einschließlich arithmetischer Operatoren, logischer Operatoren, Vergleichsoperatoren und Mengenoperatoren usw.

Rechen- und Logikoperatoren

Rechenoperatoren umfassen Addition (+), Subtraktion (-), Multiplikation (*), Division (/) und Modulo (%). Logikoperatoren umfassen logisches UND (&&), logisches ODER (||) und logisches NICHT (!), zum Beispiel:

2 + 2 // Ergebnis ist 4
7 % 3 // Ergebnis ist 1
!true // Ergebnis ist false
age >= 18 && name == "Alice" // Überprüfen, ob das Alter nicht weniger als 18 ist und ob der Name gleich "Alice" ist

Vergleichsoperatoren

Vergleichsoperatoren umfassen Gleichheit (==), Ungleichheit (!=), kleiner als (<), kleiner oder gleich (<=), größer als (>) und größer oder gleich (>=), die zum Vergleich von zwei Werten verwendet werden:

age == 25 // Überprüfen, ob das Alter gleich 25 ist
age != 18 // Überprüfen, ob das Alter ungleich 18 ist
age > 20  // Überprüfen, ob das Alter größer als 20 ist

Mengenoperatoren

Expr bietet auch einige Operatoren zum Arbeiten mit Mengen, wie z.B. in, um zu überprüfen, ob ein Element in der Menge enthalten ist. Mengen können Arrays, Slices oder Maps sein:

"user" in ["user", "admin"]  // true, weil "user" im Array enthalten ist
3 in {1: true, 2: false}     // false, weil 3 kein Schlüssel in der Map ist

Es gibt auch einige fortgeschrittene Mengenoperationsfunktionen wie all, any, one und none, die die Verwendung anonymer Funktionen (Lambda) erfordern:

all(tweets, {.Len <= 240})  // Überprüfen, ob das Len-Feld aller Tweets nicht mehr als 240 Zeichen enthält
any(tweets, {.Len > 200})   // Überprüfen, ob in den Tweets ein Len-Feld vorhanden ist, das mehr als 200 Zeichen enthält

Member-Operator

Im Expr-Ausdrucks-Sprache ermöglicht uns der Member-Operator den Zugriff auf die Eigenschaften des struct in der Go-Sprache. Diese Funktion ermöglicht Expr die direkte Manipulation von komplexen Datenstrukturen, was sehr flexibel und praktisch ist.

Die Verwendung des Member-Operators ist sehr einfach, verwenden Sie einfach den .-Operator gefolgt vom Eigenschaftsnamen. Wenn wir beispielsweise die folgende struct haben:

type User struct {
    Name string
    Age  int
}

Können Sie einen Ausdruck schreiben, um die Name-Eigenschaft der User-Struktur wie folgt abzurufen:

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

code := `user.Name`

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) // Ergebnis: Alice

Umgang mit Nullwerten

Beim Zugriff auf Eigenschaften können Sie auf Situationen stoßen, in denen das Objekt nil ist. Expr bietet sicheren Eigenschaftszugriff, sodass selbst wenn das struct oder die verschachtelte Eigenschaft nil ist, kein Laufzeit-Panicfehler ausgelöst wird.

Verwenden Sie den ?.-Operator, um auf Eigenschaften zu verweisen. Wenn das Objekt nil ist, wird es anstelle eines Fehlers nil zurückgeben.

author.User?.Name

Äquivalenter Ausdruck

author.User != nil ? author.User.Name : nil

Die Verwendung des ??-Operators dient hauptsächlich zur Rückgabe von Standardwerten:

author.User?.Name ?? "Anonymous"

Äquivalenter Ausdruck

author.User != nil ? author.User.Name : "Anonymous"

Pipe-Operator

Der Pipe-Operator (|) in Expr wird verwendet, um das Ergebnis eines Ausdrucks als Parameter an einen anderen Ausdruck zu übergeben. Dies ähnelt der Pipe-Operation in der Unix-Shell, die es ermöglicht, mehrere Funktionsmodule miteinander zu verketten, um eine Verarbeitungspipeline zu bilden. In Expr kann seine Verwendung zu klareren und prägnanteren Ausdrücken führen.

Wenn wir beispielsweise eine Funktion haben, um den Namen eines Benutzers und eine Vorlage für eine Begrüßungsnachricht zu erhalten:

env := map[string]interface{}{
    "user":      User{Name: "Bob", Age: 30},
    "get_name":  func(u User) string { return u.Name },
    "greet_msg": "Hallo, %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) // Ausgabe: Hallo, Bob!

In diesem Beispiel erhalten wir zunächst den Namen des Benutzers durch get_name(user) und geben dann den Namen mit dem Pipe-Operator | an die Funktion sprintf weiter, um die endgültige Begrüßungsnachricht zu generieren.

Die Verwendung des Pipe-Operators kann unseren Code modularisieren, die Wiederverwendbarkeit des Codes verbessern und die Ausdrücke lesbarer machen.

4.3 Funktionen

Expr unterstützt eingebaute Funktionen und benutzerdefinierte Funktionen, um Ausdrücke leistungsstärker und flexibler zu gestalten.

Verwendung von eingebauten Funktionen

Eingebaute Funktionen wie len, all, none, any, usw. können direkt im Ausdruck verwendet werden.

// Beispiel zur Verwendung einer eingebauten Funktion
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
    panic(err)
}

// Hinweis: Hier muss env die Variable users enthalten und jeder Benutzer muss die Eigenschaft Age haben
output, err := expr.Run(program, env)
fmt.Print(output) // Wenn alle Benutzer in env 18 Jahre oder älter sind, wird true zurückgegeben

Definition und Verwendung benutzerdefinierter Funktionen

In Expr können benutzerdefinierte Funktionen erstellt werden, indem Funktionsdefinitionen in die Umgebungsabbildung übergeben werden.

// Beispiel einer benutzerdefinierten Funktion
env := map[string]interface{}{
    "greet": func(name string) string {
        return fmt.Sprintf("Hallo, %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) // Gibt Hallo, World! zurück

Bei der Verwendung von Funktionen in Expr können Sie Ihren Code modularisieren und komplexe Logik in die Ausdrücke integrieren. Durch die Kombination von Variablen, Operatoren und Funktionen wird Expr zu einem leistungsstarken und einfach zu verwendenden Werkzeug. Denken Sie immer daran, die Typsicherheit beim Erstellen der Expr-Umgebung und Ausführen von Ausdrücken zu gewährleisten.

5. Dokumentation zu eingebauten Funktionen

Die Expr-Ausdrucks-Engine stellt Entwicklern eine Vielzahl von eingebauten Funktionen zur Verfügung, um verschiedene komplexe Szenarien zu bewältigen. Im Folgenden werden diese eingebauten Funktionen und ihre Verwendung im Detail erläutert.

all

Die Funktion all kann verwendet werden, um zu überprüfen, ob alle Elemente in einer Sammlung eine bestimmte Bedingung erfüllen. Sie nimmt zwei Parameter an: die Sammlung und den Bedingungsausdruck.

// Überprüfen, ob alle Tweets eine Inhaltslänge von weniger als 240 haben
code := `all(tweets, len(.Content) < 240)`

any

Ähnlich wie all wird die Funktion any verwendet, um zu überprüfen, ob ein Element in einer Sammlung eine Bedingung erfüllt.

// Überprüfen, ob ein Tweet eine Inhaltslänge von mehr als 240 hat
code := `any(tweets, len(.Content) > 240)`

none

Die Funktion none wird verwendet, um zu überprüfen, ob kein Element in einer Sammlung eine Bedingung erfüllt.

// Sicherstellen, dass keine Tweets wiederholt werden
code := `none(tweets, .IsRepeated)`

one

Die Funktion one wird verwendet, um sicherzustellen, dass nur ein Element in einer Sammlung eine Bedingung erfüllt.

// Überprüfen, ob nur ein Tweet ein bestimmtes Schlüsselwort enthält
code := `one(tweets, contains(.Content, "keyword"))`

filter

Die Funktion filter kann Elemente einer Sammlung filtern, die eine bestimmte Bedingung erfüllen.

// Herausfiltern aller Tweets, die als Priorität markiert sind
code := `filter(tweets, .IsPriority)`

map

Die Funktion map wird verwendet, um Elemente einer Sammlung gemäß einer bestimmten Methode zu transformieren.

// Formatieren der Veröffentlichungszeit aller Tweets
code := `map(tweets, {.PublishTime: Format(.Date)})`

len

Die Funktion len wird verwendet, um die Länge einer Sammlung oder eines Strings zurückzugeben.

// Die Länge des Benutzernamens abrufen
code := `len(username)`

contains

Die Funktion contains wird verwendet, um zu überprüfen, ob ein String einen Teilstring enthält oder ob eine Sammlung ein bestimmtes Element enthält.

// Überprüfen, ob der Benutzername illegale Zeichen enthält
code := `contains(username, "illegale Zeichen")`

Die oben genannten Funktionen sind nur ein Teil der integrierten Funktionen, die von der Expr-Ausdruckssprache bereitgestellt werden. Mit diesen leistungsstarken Funktionen können Sie Daten und Logik flexibler und effizienter bearbeiten. Für eine ausführlichere Liste von Funktionen und Anwendungshinweisen verweisen wir auf die offizielle Expr-Dokumentation.