- Go 98.3%
- HTML 1.7%
|
|
||
|---|---|---|
| .woodpecker | ||
| docs | ||
| examples | ||
| pkg | ||
| tests | ||
| .gitignore | ||
| .woodpecker.yml | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
go-interpret-agregate
Единый Go-интерфейс для выполнения скриптов на JavaScript (Goja), Lua (Golua) и Go (Yaegi) — с ограничением ресурсов, подключением host-функций и строгой валидацией типов.
Возможности
- Три бэкенда — один интерфейс — переключайтесь между Goja, Golua и Yaegi без изменения кода
- Ограничение ресурсов — CPU для всех бэкендов, память для Golua, блокировка горутин для Yaegi
- Host-функции — регистрация Go-функций, вызываемых из скриптов через
internal.*иexternal.* - Строгая валидация типов — опциональные схемы параметров для ранней проверки до выполнения
- Безопасность по умолчанию — файловая система, сеть и системные вызовы заблокированы
Быстрый старт
go get git.ymnuktech.ru/ymnuk/go-interpret-agregate
package main
import (
"fmt"
"git.ymnuktech.ru/ymnuk/go-interpret-agregate/pkg/factory"
"git.ymnuktech.ru/ymnuk/go-interpret-agregate/pkg/interpreter"
)
func main() {
// Создаём интерпретатор (замените TypeGolua → TypeGoja → TypeYaegi для смены бэкенда)
interp, err := factory.NewInterpreter(factory.TypeGolua)
if err != nil {
panic(err)
}
defer interp.Close()
// Загружаем скрипт
script := `
function greet(name)
return "Hello, " .. name
end
`
if err := interp.PrepareScript(script); err != nil {
panic(err)
}
// Вызываем функцию
result, err := interp.Execute("greet", []interface{}{"World"}, nil)
if err != nil {
panic(err)
}
fmt.Println(result.Value) // Hello, World
}
Поддерживаемые бэкенды
| Бэкенд | Язык | Лимит CPU | Лимит памяти | Блокировка потоков |
|---|---|---|---|---|
| Goja | JavaScript (ES5.1) | ✅ По таймеру | ❌ Игнорируется | ✅ Не применимо (синхронный) |
| Golua | Lua | ✅ Встроенный | ✅ Встроенный | ✅ Не применимо (нет потоков) |
| Yaegi | Go | ✅ Через context | ❌ Игнорируется | ✅ AST-анализ |
Важные ограничения
MaxMemoryработает только для Golua. Для Goja и Yaengi память управляется Go runtime и не может быть ограничена для отдельной горутины.Threadsприменяется только к Yaegi (блокируетgo/selectчерез AST-анализ). У Goja нет event loop, у Lua нет многопоточности.- Goja гарантирует поддержку ES5.1. Возможности ES6+ (
let,const,class,async/await) — в разработке. Для гарантированного выполнения используйте транспиляцию черезtsc --target ES5 --module none.
Использование
Фабрика
// Простое создание
interp, err := factory.NewInterpreter(factory.TypeGoja)
// С конфигурацией (лимиты + host-функции)
config := factory.Config{
Type: factory.TypeGolua,
Limits: interpreter.Limits{
MaxCPUTime: time.Second,
MaxMemory: 64 * 1024 * 1024,
Threads: false, // безопасный режим по умолчанию
},
Functions: []factory.FunctionConfig{
{
Namespace: "internal",
Name: "ReadFile",
Fn: myReadFileFn,
Params: []interpreter.Param{{Type: "string"}},
},
},
}
interp, err := factory.NewInterpreterFromConfig(config)
Жизненный цикл
1. NewInterpreter() → создание экземпляра
2. AddFunction(...) → регистрация host-функций (опционально)
3. PrepareScript(script) → парсинг и загрузка скрипта
4. Execute("funcName", args) → вызов функции (повторяемый)
5. Close() → освобождение ресурсов
Host-функции
Регистрация Go-функций, вызываемых из скриптов:
// Регистрируем функцию со строгой валидацией типов
interp.AddFunction("internal", "ReadFile", func(args []interface{}) (interface{}, error) {
path := args[0].(string)
return os.ReadFile(path)
}, []interpreter.Param{{Type: "string"}})
// Вызов из JavaScript:
// var data = internal.ReadFile("/path/to/file")
// Вызов из Lua:
// local data = internal.ReadFile("/path/to/file")
// Вызов из Go (Yaegi):
// import "internal"
// data, err := internal.ReadFile("/path/to/file")
Строгая валидация типов
При указании params метод Execute проверяет типы аргументов до выполнения скрипта:
// Корректно: float64(5.0) → int (без потери точности)
interp.Execute("fn", []interface{}{float64(5.0)}, nil)
// Ошибка: float64(5.1) → int (потеря дробной части)
// → error: argument 0: expected int, got 5.1
Поддерживаемые типы: int, int8–int64, uint–uint64, float32, float64, string, bool, []byte, []string, []int, []int64, []float64, map[string]interface{}, interface{}.
Ограничение ресурсов
result, err := interp.Execute("heavyFn", args, &interpreter.ExecuteOptions{
Limits: interpreter.Limits{
MaxCPUTime: 500 * time.Millisecond,
MaxMemory: 32 * 1024 * 1024, // Эффективно только для Golua
},
})
if errors.Is(err, interpreter.ErrTimeout) {
// Обработка таймаута
}
Yaegi: блокировка горутин
По умолчанию Threads=false — скрипты Yaegi не могут использовать go и select:
// Этот скрипт будет отклонён:
script := `package main; func Run() { go func(){}() }`
err := interp.PrepareScript(script)
// → ErrThreadsNotAllowed
Для разрешения горутин (небезопасно — фоновые горутины не останавливаются при таймауте):
y := yaegi.New()
y.AllowThreads()
Обработка ошибок
Все бэкенды возвращают унифицированный interpreter.Result:
type Result struct {
Value interface{} // Возвращаемое значение (скаляр, map, слайс и т.д.)
Error string // Текст ошибки (пусто при успехе)
TimedOut bool // True если выполнение прервано по таймауту
}
Сентинел-ошибки для type checking:
errors.Is(err, interpreter.ErrTimeout) // выполнение прервано по таймауту
errors.Is(err, interpreter.ErrThreadsNotAllowed) // go/select заблокированы
errors.Is(err, interpreter.ErrMemoryLimit) // превышен лимит памяти (Golua)
errors.Is(err, interpreter.ErrFunctionNotFound) // функция не найдена
errors.Is(err, interpreter.ErrScriptParse) // синтаксическая ошибка
errors.Is(err, interpreter.ErrNameConflict) // коллизия имён host-функции и скрипта
Структура проекта
├── pkg/
│ ├── interpreter/ # Базовый интерфейс, типы, валидация
│ ├── goja/ # JavaScript бэкенд (Goja)
│ ├── golua/ # Lua бэкенд (Golua)
│ ├── yaegi/ # Go бэкенд (Yaegi)
│ └── factory/ # Фабричные функции
├── tests/
│ ├── integration/ # Интеграционные тесты
│ └── benchmarks/ # Кросс-бэкенд бенчмарки
├── examples/ # Примеры использования
└── docs/ # Проектная документация
Тестирование
# Unit-тесты
go test -v ./pkg/...
# Интеграционные тесты
go test -v ./tests/integration/...
# Бенчмарки
go test -bench=. -benchmem ./tests/benchmarks/...
# Покрытие
go test -coverprofile=coverage.out ./pkg/... ./tests/...
go tool cover -html=coverage.out
CI/CD
Woodpecker CI (.woodpecker.yml):
- Линтинг —
go fmt+go vet - Unit-тесты —
go test -short -v ./... - Покрытие —
go test -coverprofile(только push) - Бенчмарки —
go test -bench=. -benchmem(только main) - Email-уведомления — отчёты по покрытию и бенчмаркам
Документация
docs/ANALYSIS.md— детальный анализ каждого бэкендаdocs/ROADMAP.md— дорожная карта и архитектурные решения
Лицензия
См. файл LICENSE.