1. Введение
Expr - это динамическое решение для конфигурации, разработанное для языка Go и известное своим простым синтаксисом и мощными функциями производительности. Основой движка выражений Expr является безопасность, скорость и интуитивность, что делает его подходящим для сценариев, таких как контроль доступа, фильтрация данных и управление ресурсами. Применение Expr в Go значительно повышает способность приложений обрабатывать динамические правила. В отличие от интерпретаторов или скриптовых движков в других языках, Expr использует статическую проверку типов и генерацию байткода для выполнения, обеспечивая как производительность, так и безопасность.
2. Установка Expr
Движок выражений Expr можно установить с помощью инструмента управления пакетами языка Go go get
:
go get github.com/expr-lang/expr
Эта команда загрузит файлы библиотеки Expr и установит их в ваш проект Go, позволяя импортировать и использовать Expr в вашем коде Go.
3. Быстрый старт
3.1 Компиляция и запуск базовых выражений
Давайте начнем с базового примера: написание простого выражения, его компиляция и выполнение для получения результата.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// Компиляция основного выражения сложения
program, err := expr.Compile(`2 + 2`)
if err != nil {
panic(err)
}
// Запуск скомпилированного выражения без передачи окружения, так как здесь не требуются переменные
output, err := expr.Run(program, nil)
if err != nil {
panic(err)
}
// Вывод результата
fmt.Println(output) // Выводит 4
}
В этом примере выражение 2 + 2
компилируется в исполняемый байткод, который затем выполняется для получения результата.
3.2 Использование переменных в выражениях
Затем создадим окружение, содержащее переменные, напишем выражение, использующее эти переменные, скомпилируем и выполним это выражение.
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// Создание окружения с переменными
env := map[string]interface{}{
"foo": 100,
"bar": 200,
}
// Компиляция выражения, использующего переменные из окружения
program, err := expr.Compile(`foo + bar`, expr.Env(env))
if err != nil {
panic(err)
}
// Выполнение выражения
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
// Вывод результата
fmt.Println(output) // Выводит 300
}
В этом примере окружение env
содержит переменные foo
и bar
. Выражение foo + bar
выводит типы foo
и bar
из окружения во время компиляции и использует их значения во время выполнения для вычисления результата выражения.
4. Синтаксис Expr в деталях
4.1 Переменные и литералы
Движок выражений Expr может обрабатывать литералы общих типов данных, включая числа, строки и логические значения. Литералы - это значения данных, напрямую записанные в коде, такие как 42
, "привет"
и true
.
Числа
В Expr можно напрямую записывать целые и числа с плавающей точкой:
42 // Представляет целое число 42
3.14 // Представляет число с плавающей точкой 3.14
Строки
Строковые литералы заключаются в двойные кавычки "
или обратные кавычки ``. Например:
"привет, мир" // Строка, заключенная в двойные кавычки, поддерживает символы экранирования
`привет, мир` // Строка, заключенная в обратные кавычки, сохраняет формат строки без поддержки символов экранирования
Булевы значения
Существуют только два логических значения, true
и false
, представляющие логическое и ложное значения:
true // Логическое true
false // Логическое false
Переменные
Expr также позволяет определять переменные в окружении, а затем ссылаться на эти переменные в выражении. Например:
env := map[string]interface{}{
"возраст": 25,
"имя": "Алиса",
}
Затем в выражении можно ссылаться на возраст
и имя
:
возраст > 18 // Проверить, больше ли возраст 18 лет
имя == "Алиса" // Определить, равно ли имя "Алисе"
4.2 Операторы
Движок выражений Expr поддерживает различные операторы, включая арифметические операторы, логические операторы, операторы сравнения и операторы множеств и т. д.
Арифметические и логические операторы
Арифметические операторы включают в себя сложение (+
), вычитание (-
), умножение (*
), деление (/
) и взятие остатка (%
). Логические операторы включают в себя логическое И (&&
), логическое ИЛИ (||
) и логическое НЕ (!
), например:
2 + 2 // Результат 4
7 % 3 // Результат 1
!true // Результат false
возраст >= 18 && имя == "Alice" // Проверить, что возраст не меньше 18 и что имя равно "Alice"
Операторы сравнения
Операторы сравнения включают равно (==
), не равно (!=
), меньше (<
), меньше или равно (<=
), больше (>
), и больше или равно (>=
), используемые для сравнения двух значений:
возраст == 25 // Проверить, что возраст равен 25
возраст != 18 // Проверить, что возраст не равен 18
возраст > 20 // Проверить, что возраст больше 20
Операторы для работы с множествами
Expr также предоставляет некоторые операторы для работы с множествами, такие как in
для проверки, принадлежит ли элемент множеству. Множествами могут быть массивы, срезы или карты:
"user" in ["user", "admin"] // true, потому что "user" есть в массиве
3 in {1: true, 2: false} // false, потому что 3 не является ключом в карте
Также имеются некоторые продвинутые функции операций с множествами, такие как all
, any
, one
и none
, требующие использования анонимных функций (лямбда):
all(tweets, {.Len <= 240}) // Проверить, что поле Len во всех твитах не превышает 240
any(tweets, {.Len > 200}) // Проверить, есть ли поле Len в твитах, превышающее 200
Оператор члена
В языке выражений Expr оператор члена позволяет нам получить доступ к свойствам struct
в языке Go. Эта функция позволяет Expr напрямую манипулировать сложными структурами данных, что делает его очень гибким и практичным.
Использование оператора члена очень просто, просто используйте оператор .
перед именем свойства. Например, если у нас есть следующая struct
:
type User struct {
Name string
Age int
}
Вы можете написать выражение для доступа к свойству Name
структуры User
следующим образом:
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) // Вывод: Alice
Обработка значений null
При доступе к свойствам вы можете столкнуться с ситуациями, когда объект является nil
. Expr предоставляет безопасный доступ к свойствам, поэтому даже если структура или вложенное свойство является nil
, это не вызовет ошибку времени выполнения.
Используйте оператор ?.
для ссылки на свойства. Если объект является nil, он вернет nil вместо вызова ошибки.
author.User?.Name
Эквивалентное выражение
author.User != nil ? author.User.Name : nil
Использование оператора ??
прежде всего для возврата значений по умолчанию:
author.User?.Name ?? "Anonymous"
Эквивалентное выражение
author.User != nil ? author.User.Name : "Anonymous"
Оператор "Pipe"
Оператор "Pipe" (|
) в Expr используется для передачи результата одного выражения в качестве параметра другому выражению. Это аналогично операции "pipe" в оболочке Unix, позволяя объединять несколько функциональных модулей для создания конвейера обработки. В Expr его использование позволяет создавать более понятные и краткие выражения.
Например, если у нас есть функция получения имени пользователя и шаблон приветственного сообщения:
env := map[string]interface{}{
"user": User{Name: "Bob", Age: 30},
"get_name": func(u User) string { return u.Name },
"greet_msg": "Привет, %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) // Вывод: Привет, Bob!
В этом примере мы сначала получаем имя пользователя с помощью get_name(user)
, затем передаем имя функции sprintf
, используя оператор "Pipe" |
, для создания окончательного приветственного сообщения.
Использование оператора "Pipe" позволяет модуляризировать наш код, улучшить его повторное использование и сделать выражения более читаемыми.
4.3 Функции
Expr поддерживает встроенные функции и пользовательские функции, что делает выражения более мощными и гибкими.
Использование встроенных функций
Встроенные функции, такие как len
, all
, none
, any
и т. д., могут быть использованы напрямую в выражении.
// Пример использования встроенной функции
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
panic(err)
}
// Примечание: здесь env должен содержать переменную users, и каждый пользователь должен иметь свойство Age
output, err := expr.Run(program, env)
fmt.Print(output) // Если все пользователи в env достигли 18 лет или старше, вернется true
Как определить и использовать пользовательские функции
В Expr вы можете создавать пользовательские функции, передавая определения функций в карту окружения.
// Пример пользовательской функции
env := map[string]interface{}{
"greet": func(name string) string {
return fmt.Sprintf("Привет, %s!", name)
},
}
program, err := expr.Compile(`greet("Мир")`, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
fmt.Print(output) // Возвращает Привет, Мир!
При использовании функций в Expr вы можете модуляризировать свой код и включать сложную логику в выражения. Путем объединения переменных, операторов и функций Expr становится мощным и простым в использовании инструментом. Помните всегда обеспечивать безопасность типов при построении окружения Expr и выполнении выражений.
5. Документация по встроенным функциям
Движок выражений Expr предоставляет разработчикам широкий набор встроенных функций для обработки различных сложных сценариев. Ниже мы подробно рассмотрим эти встроенные функции и их использование.
all
Функция all
может использоваться для проверки, удовлетворяют ли все элементы коллекции заданному условию. Она принимает два параметра: коллекцию и выражение условия.
// Проверяем, удовлетворяют ли все твиты условию длины содержимого менее 240 символов
code := `all(tweets, len(.Content) < 240)`
any
Аналогично all
, функция any
используется для проверки, удовлетворяет ли какой-либо элемент коллекции условию.
// Проверяем, удовлетворяет ли какой-либо твит условию длины содержимого более 240 символов
code := `any(tweets, len(.Content) > 240)`
none
Функция none
используется для проверки, удовлетворяет ли какой-либо элемент коллекции заданному условию.
// Убеждаемся, что ни один твит не повторяется
code := `none(tweets, .IsRepeated)`
one
Функция one
используется для подтверждения того, что только один элемент коллекции удовлетворяет условию.
// Проверяем, содержит ли только один твит определенное ключевое слово
code := `one(tweets, contains(.Content, "ключевое слово"))`
filter
Функция filter
используется для фильтрации элементов коллекции, удовлетворяющих заданному условию.
// Фильтрация всех твитов, отмеченных как приоритетные
code := `filter(tweets, .IsPriority)`
map
Функция map
используется для преобразования элементов коллекции в соответствии с указанным методом.
// Форматирование времени публикации всех твитов
code := `map(tweets, {.PublishTime: Format(.Date)})`
len
Функция len
используется для возврата длины коллекции или строки.
// Получение длины имени пользователя
code := `len(username)`
contains
Функция contains
используется для проверки, содержит ли строка подстроку или содержит ли коллекция определенный элемент.
// Проверка, содержит ли имя пользователя недопустимые символы
code := `contains(username, "недопустимые символы")`
Упомянутые выше функции представляют лишь часть встроенных функций, предоставляемых движком выражений Expr. С их помощью вы можете более гибко и эффективно обрабатывать данные и логику. Для более подробного списка функций и инструкций по использованию обратитесь к официальной документации по Expr.