1. Introduction

Expr est une solution de configuration dynamique conçue pour le langage Go, reconnue pour sa syntaxe simple et ses performances puissantes. Le cœur du moteur d'expression Expr se concentre sur la sécurité, la vitesse et l'intuitivité, le rendant adapté à des scénarios tels que le contrôle d'accès, le filtrage de données et la gestion des ressources. Lorsqu'il est appliqué à Go, Expr améliore considérablement la capacité des applications à gérer des règles dynamiques. Contrairement aux interprètes ou aux moteurs de script dans d'autres langages, Expr adopte une vérification de type statique et génère un bytecode pour l'exécution, garantissant à la fois des performances et une sécurité.

2. Installation de Expr

Vous pouvez installer le moteur d'expression Expr en utilisant l'outil de gestion de packages du langage Go go get:

go get github.com/expr-lang/expr

Cette commande téléchargera les fichiers de la bibliothèque Expr et les installera dans votre projet Go, vous permettant d'importer et d'utiliser Expr dans votre code Go.

3. Démarrage rapide

3.1 Compilation et exécution des expressions de base

Commençons par un exemple simple : écrire une expression simple, la compiler, puis l'exécuter pour obtenir le résultat.

package main

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

func main() {
	// Compilation d'une addition de base
	program, err := expr.Compile(`2 + 2`)
	if err != nil {
		panic(err)
	}

	// Exécution de l'expression compilée sans passer d'environnement, car aucune variable n'est nécessaire ici
	output, err := expr.Run(program, nil)
	if err != nil {
		panic(err)
	}

	// Affichage du résultat
	fmt.Println(output)  // Affiche 4
}

Dans cet exemple, l'expression 2 + 2 est compilée en bytecode exécutable, qui est ensuite exécuté pour produire la sortie.

3.2 Utilisation d'expressions avec des variables

Ensuite, nous allons créer un environnement contenant des variables, écrire une expression qui utilise ces variables, la compiler et exécuter cette expression.

package main

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

func main() {
	// Création d'un environnement avec des variables
	env := map[string]interface{}{
		"foo": 100,
		"bar": 200,
	}

	// Compilation d'une expression utilisant des variables de l'environnement
	program, err := expr.Compile(`foo + bar`, expr.Env(env))
	if err != nil {
		panic(err)
	}

	// Exécution de l'expression
	output, err := expr.Run(program, env)
	if err != nil {
		panic(err)
	}

	// Affichage du résultat
	fmt.Println(output)  // Affiche 300
}

Dans cet exemple, l'environnement env contient les variables foo et bar. L'expression foo + bar déduit les types de foo et bar à partir de l'environnement lors de la compilation, et utilise les valeurs de ces variables à l'exécution pour évaluer le résultat de l'expression.

4. Syntaxe de Expr en détail

4.1 Variables et littéraux

Le moteur d'expression Expr peut manipuler des littéraux de types de données courants, y compris des nombres, des chaînes de caractères et des valeurs booléennes. Les littéraux sont des valeurs de données directement écrites dans le code, telles que 42, "hello" et true.

Nombres

En Expr, vous pouvez écrire directement des entiers et des nombres à virgule flottante :

42      // Représente l'entier 42
3.14    // Représente le nombre à virgule flottante 3.14

Chaînes de caractères

Les littéraux de chaînes sont encadrés de guillemets doubles " ou d'accent graves ``. Par exemple :

"hello, world" // Chaîne encadrée de guillemets doubles, prend en charge les caractères d'échappement
`hello, world` // Chaîne encadrée d'accent graves, maintient le format de la chaîne sans prendre en charge les caractères d'échappement

Booléens

Il n'y a que deux valeurs booléennes, true and false, représentant vrai et faux logiques :

true   // Valeur booléenne vrai
false  // Valeur booléenne faux

Variables

Expr permet également la définition de variables dans l'environnement, puis fait référence à ces variables dans l'expression. Par exemple :

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

Ensuite, dans l'expression, vous pouvez faire référence à age et name:

age > 18  // Vérifie si l'âge est supérieur à 18
name == "Alice"  // Détermine si le nom est égal à "Alice"

4.2 Opérateurs

Le moteur d'expression Expr prend en charge divers opérateurs, notamment des opérateurs arithmétiques, logiques, de comparaison et d'ensemble, etc.

Opérateurs arithmétiques et logiques

Les opérateurs arithmétiques incluent l'addition (+), la soustraction (-), la multiplication (*), la division (/) et le modulo (%). Les opérateurs logiques incluent le ET logique (&&), le OU logique (||) et le NON logique (!), par exemple :

2 + 2 // Le résultat est 4
7 % 3 // Le résultat est 1
!true // Le résultat est faux
age >= 18 && name == "Alice" // Vérifie si l'âge n'est pas inférieur à 18 et si le nom est égal à "Alice"

Opérateurs de comparaison

Les opérateurs de comparaison incluent égal à (==), différent de (!=), inférieur à (<), inférieur ou égal à (<=), supérieur à (>) et supérieur ou égal à (>=), utilisés pour comparer deux valeurs :

age == 25 // Vérifie si l'âge est égal à 25
age != 18 // Vérifie si l'âge n'est pas égal à 18
age > 20  // Vérifie si l'âge est supérieur à 20

Opérateurs d'ensemble

Expr fournit également certains opérateurs pour travailler avec des ensembles, tels que in pour vérifier si un élément est dans l'ensemble. Les ensembles peuvent être des tableaux, des tranches ou des cartes :

"user" in ["user", "admin"]  // vrai, car "user" est dans le tableau
3 in {1: true, 2: false}     // faux, car 3 n'est pas une clé dans la carte

Il existe également quelques fonctions avancées d'opération sur les ensembles, telles que all, any, one et none, qui nécessitent l'utilisation de fonctions anonymes (lambda) :

all(tweets, {.Len <= 240})  // Vérifie si le champ Len de tous les tweets ne dépasse pas 240
any(tweets, {.Len > 200})   // Vérifie s'il existe un champ Len dans les tweets qui dépasse 200

Opérateur de membre

Dans le langage d'expression Expr, l'opérateur de membre nous permet d'accéder aux propriétés de la struct en langage Go. Cette fonctionnalité permet à Expr de manipuler directement des structures de données complexes, le rendant très flexible et pratique.

L'utilisation de l'opérateur de membre est très simple, il suffit d'utiliser l'opérateur . suivi du nom de la propriété. Par exemple, si nous avons la struct suivante :

type User struct {
    Name string
    Age  int
}

Vous pouvez écrire une expression pour accéder à la propriété Name de la structure User comme ceci :

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) // Résultat : Alice

Gestion des valeurs nulles

Lors de l'accès aux propriétés, vous pouvez rencontrer des situations où l'objet est nil. Expr fournit un accès sécurisé aux propriétés, donc même si la structure ou la propriété imbriquée est nil, cela ne provoquera pas d'erreur de panique à l'exécution.

Utilisez l'opérateur ?. pour référencer les propriétés. Si l'objet est nul, il renverra nul au lieu de déclencher une erreur.

author.User?.Name

Expression équivalente

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

L'utilisation de l'opérateur ?? est principalement pour retourner des valeurs par défaut :

author.User?.Name ?? "Anonyme"

Expression équivalente

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

Opérateur de tube

L'opérateur de tube (|) dans Expr est utilisé pour passer le résultat d'une expression en tant que paramètre à une autre expression. Cela est similaire à l'opération de tube dans le shell Unix, permettant à plusieurs modules fonctionnels d'être enchaînés pour former un pipeline de traitement. Dans Expr, son utilisation peut créer des expressions plus claires et concises.

Par exemple, si nous avons une fonction pour obtenir le nom d'un utilisateur et un modèle pour un message de bienvenue :

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

Dans cet exemple, nous obtenons d'abord le nom de l'utilisateur via get_name(user), puis passons le nom à la fonction sprintf en utilisant l'opérateur de tube | pour générer le message de bienvenue final.

L'utilisation de l'opérateur de tube peut modulariser notre code, améliorer la réutilisabilité du code et rendre les expressions plus lisibles.

4.3 Fonctions

Expr prend en charge les fonctions intégrées et les fonctions personnalisées, ce qui rend les expressions plus puissantes et flexibles.

Utilisation des fonctions intégrées

Des fonctions intégrées telles que len, all, none, any, etc. peuvent être utilisées directement dans l'expression.

// Exemple d'utilisation d'une fonction intégrée
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
    panic(err)
}

// Remarque : ici, l'environnement doit contenir la variable users, et chaque utilisateur doit avoir la propriété Age
output, err := expr.Run(program, env)
fmt.Print(output) // Si tous les utilisateurs dans l'environnement ont 18 ans ou plus, cela renverra vrai

Comment définir et utiliser des fonctions personnalisées

Dans Expr, vous pouvez créer des fonctions personnalisées en passant des définitions de fonctions dans la cartographie de l'environnement.

// Exemple de fonction personnalisée
env := map[string]interface{}{
    "greet": func(name string) string {
        return fmt.Sprintf("Bonjour, %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) // Renvoie Bonjour, World!

Lors de l'utilisation de fonctions dans Expr, vous pouvez modulariser votre code et incorporer une logique complexe dans les expressions. En combinant des variables, des opérateurs et des fonctions, Expr devient un outil puissant et facile à utiliser. N'oubliez pas de toujours garantir la sécurité des types lors de la construction de l'environnement Expr et de l'exécution des expressions.

5. Documentation des fonctions intégrées

Le moteur d'expression Expr fournit aux développeurs un ensemble riche de fonctions intégrées pour gérer divers scénarios complexes. Ci-dessous, nous détaillerons ces fonctions intégrées et leur utilisation.

all

La fonction all peut être utilisée pour vérifier si tous les éléments d'une collection satisfont une condition donnée. Elle prend deux paramètres : la collection et l'expression de condition.

// Vérifier si tous les tweets ont une longueur de contenu inférieure à 240
code := `all(tweets, len(.Content) < 240)`

any

Similaire à all, la fonction any est utilisée pour vérifier si un élément quelconque dans une collection satisfait une condition.

// Vérifier si un tweet a une longueur de contenu supérieure à 240
code := `any(tweets, len(.Content) > 240)`

none

La fonction none est utilisée pour vérifier si aucun élément dans une collection ne satisfait une condition.

// S'assurer qu'aucun tweet n'est répété
code := `none(tweets, .IsRepeated)`

one

La fonction one est utilisée pour confirmer que seul un élément dans une collection satisfait une condition.

// Vérifier si un seul tweet contient un mot-clé spécifique
code := `one(tweets, contains(.Content, "mot-clé"))`

filter

La fonction filter peut filtrer les éléments de collection qui satisfont une condition donnée.

// Filtrer tous les tweets marqués comme prioritaires
code := `filter(tweets, .IsPriority)`

map

La fonction map est utilisée pour transformer les éléments d'une collection selon une méthode spécifiée.

// Formater l'heure de publication de tous les tweets
code := `map(tweets, {.PublishTime: Format(.Date)})`

len

La fonction len est utilisée pour retourner la longueur d'une collection ou d'une chaîne de caractères.

// Obtenir la longueur du nom d'utilisateur
code := `len(username)`

contains

La fonction contains est utilisée pour vérifier si une chaîne de caractères contient une sous-chaîne de caractères ou si une collection contient un élément spécifique.

// Vérifier si le nom d'utilisateur contient des caractères illégaux
code := `contains(username, "caractères illégaux")`

Ceux mentionnés ci-dessus ne sont qu'une partie des fonctions intégrées fournies par le moteur d'expression Expr. Avec ces fonctions puissantes, vous pouvez manipuler les données et la logique de manière plus flexible et efficace. Pour une liste plus détaillée des fonctions et des instructions d'utilisation, veuillez vous référer à la documentation officielle d'Expr.