168 lines
4.1 KiB
Go
168 lines
4.1 KiB
Go
package modules
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"maps"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"Moonshark/modules/crypto"
|
|
"Moonshark/modules/fs"
|
|
"Moonshark/modules/json"
|
|
"Moonshark/modules/math"
|
|
lua_string "Moonshark/modules/string"
|
|
|
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
|
)
|
|
|
|
// Global registry instance
|
|
var Global *Registry
|
|
|
|
//go:embed crypto/*.lua fs/*.lua json/*.lua math/*.lua string/*.lua
|
|
var embeddedModules embed.FS
|
|
|
|
// Registry manages all Lua modules and Go functions
|
|
type Registry struct {
|
|
modules map[string]string
|
|
goFuncs map[string]luajit.GoFunction
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// New creates a new registry with all Go functions loaded
|
|
func New() *Registry {
|
|
r := &Registry{
|
|
modules: make(map[string]string),
|
|
goFuncs: make(map[string]luajit.GoFunction),
|
|
}
|
|
|
|
// Load all Go functions from each module
|
|
maps.Copy(r.goFuncs, json.GetFunctionList())
|
|
maps.Copy(r.goFuncs, lua_string.GetFunctionList())
|
|
maps.Copy(r.goFuncs, math.GetFunctionList())
|
|
maps.Copy(r.goFuncs, fs.GetFunctionList())
|
|
maps.Copy(r.goFuncs, crypto.GetFunctionList())
|
|
|
|
return r
|
|
}
|
|
|
|
// LoadEmbeddedModules loads all .lua files from embedded filesystem
|
|
func (r *Registry) LoadEmbeddedModules() error {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
// Load modules from subdirectories
|
|
subdirs := []string{"crypto", "fs", "json", "math", "string"}
|
|
for _, subdir := range subdirs {
|
|
if err := r.loadModulesFromDir(subdir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadModulesFromDir loads all .lua files from a specific directory
|
|
func (r *Registry) loadModulesFromDir(dir string) error {
|
|
entries, err := embeddedModules.ReadDir(dir)
|
|
if err != nil {
|
|
return nil // Skip missing directories
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".lua") {
|
|
continue
|
|
}
|
|
|
|
fileName := strings.TrimSuffix(entry.Name(), ".lua")
|
|
|
|
// If filename matches directory name (e.g., math/math.lua),
|
|
// register as just the directory name for require("math")
|
|
var moduleName string
|
|
if fileName == dir {
|
|
moduleName = dir
|
|
} else {
|
|
moduleName = dir + "/" + fileName
|
|
}
|
|
|
|
source, err := embeddedModules.ReadFile(filepath.Join(dir, entry.Name()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read module %s: %w", moduleName, err)
|
|
}
|
|
|
|
r.modules[moduleName] = string(source)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// InstallInState sets up the complete module system in a Lua state
|
|
func (r *Registry) InstallInState(state *luajit.State) error {
|
|
r.mutex.RLock()
|
|
defer r.mutex.RUnlock()
|
|
|
|
// Create moonshark global table
|
|
state.NewTable()
|
|
state.SetGlobal("moonshark")
|
|
|
|
// Install Go functions
|
|
state.GetGlobal("moonshark")
|
|
for name, fn := range r.goFuncs {
|
|
if err := state.PushGoFunction(fn); err != nil {
|
|
return fmt.Errorf("failed to register Go function '%s': %w", name, err)
|
|
}
|
|
state.SetField(-2, name)
|
|
}
|
|
state.Pop(1) // Remove moonshark table
|
|
|
|
// Backup original require
|
|
state.GetGlobal("require")
|
|
state.SetGlobal("_require_original")
|
|
|
|
// Install custom require function
|
|
return state.RegisterGoFunction("require", func(s *luajit.State) int {
|
|
if err := s.CheckMinArgs(1); err != nil {
|
|
return s.PushError("require: %v", err)
|
|
}
|
|
|
|
moduleName, err := s.SafeToString(1)
|
|
if err != nil {
|
|
return s.PushError("require: module name must be a string")
|
|
}
|
|
|
|
// Check built-in modules first
|
|
r.mutex.RLock()
|
|
source, exists := r.modules[moduleName]
|
|
r.mutex.RUnlock()
|
|
|
|
if exists {
|
|
if err := s.LoadString(source); err != nil {
|
|
return s.PushError("require: failed to load module '%s': %v", moduleName, err)
|
|
}
|
|
if err := s.Call(0, 1); err != nil {
|
|
return s.PushError("require: failed to execute module '%s': %v", moduleName, err)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// Fall back to original require
|
|
s.GetGlobal("_require_original")
|
|
if s.IsFunction(-1) {
|
|
s.PushString(moduleName)
|
|
if err := s.Call(1, 1); err != nil {
|
|
return s.PushError("require: %v", err)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
return s.PushError("require: module '%s' not found", moduleName)
|
|
})
|
|
}
|
|
|
|
// Initialize sets up the global registry with all modules loaded
|
|
func Initialize() error {
|
|
Global = New()
|
|
return Global.LoadEmbeddedModules()
|
|
}
|