No description
  • Go 98.3%
  • HTML 1.7%
Find a file
Ymnuk 5b57bb8d35
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Бенчмарки, более приближенные к реальному использованию
2026-04-07 14:37:01 +03:00
.woodpecker Документация 2026-04-07 13:37:25 +03:00
docs Документация 2026-04-07 13:37:25 +03:00
examples Документация 2026-04-07 13:37:25 +03:00
pkg Документация 2026-04-07 13:37:25 +03:00
tests Бенчмарки, более приближенные к реальному использованию 2026-04-07 14:37:01 +03:00
.gitignore Goja 2026-04-06 13:47:25 +03:00
.woodpecker.yml CI/CD 2026-04-07 13:08:50 +03:00
go.mod Golua b Yaegi 2026-04-06 14:34:58 +03:00
go.sum Golua b Yaegi 2026-04-06 14:34:58 +03:00
LICENSE Документация 2026-04-06 13:16:46 +03:00
README.md Документация 2026-04-07 13:37:25 +03:00

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, int8int64, uintuint64, 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):

  1. Линтингgo fmt + go vet
  2. Unit-тестыgo test -short -v ./...
  3. Покрытиеgo test -coverprofile (только push)
  4. Бенчмаркиgo test -bench=. -benchmem (только main)
  5. Email-уведомления — отчёты по покрытию и бенчмаркам

Документация

  • docs/ANALYSIS.md — детальный анализ каждого бэкенда
  • docs/ROADMAP.md — дорожная карта и архитектурные решения

Лицензия

См. файл LICENSE.