From e86cb55aa6f563bf9014b545aaedc297e6d9de89 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Thu, 17 Jul 2025 13:46:18 -0500 Subject: [PATCH] introduce state management --- moonshark.go | 64 ++---------- state/state.go | 266 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 56 deletions(-) create mode 100644 state/state.go diff --git a/moonshark.go b/moonshark.go index 03c6813..959f905 100644 --- a/moonshark.go +++ b/moonshark.go @@ -5,9 +5,7 @@ import ( "os" "path/filepath" - "Moonshark/modules" - - luajit "git.sharkk.net/Sky/LuaJIT-to-Go" + "Moonshark/state" ) func main() { @@ -18,63 +16,17 @@ func main() { scriptPath := os.Args[1] - // Check if file exists - if _, err := os.Stat(scriptPath); os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "Error: script file '%s' not found\n", scriptPath) - os.Exit(1) - } - - // Initialize global registry - if err := modules.Initialize(); err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to initialize registry: %v\n", err) - os.Exit(1) - } - - // Create new Lua state with standard libraries - state := luajit.New() - if state == nil { - fmt.Fprintf(os.Stderr, "Error: failed to create Lua state\n") - os.Exit(1) - } - defer state.Close() - - // Install module system in main state - if err := modules.Global.InstallInState(state); err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to install module system: %v\n", err) - os.Exit(1) - } - - // Get the absolute path to the script directory - scriptDir := filepath.Dir(scriptPath) - absScriptDir, err := filepath.Abs(scriptDir) + // Create state configured for the script + luaState, err := state.NewFromScript(scriptPath) if err != nil { - fmt.Fprintf(os.Stderr, "Error: failed to get absolute path: %v\n", err) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } + defer luaState.Close() - // Add script directory to Lua's package.path - packagePath := filepath.Join(absScriptDir, "?.lua") - if err := state.AddPackagePath(packagePath); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to add script directory to package.path: %v\n", err) - } - - // Read script file - scriptContent, err := os.ReadFile(scriptPath) - if err != nil { - fmt.Fprintf(os.Stderr, "Error reading script file '%s': %v\n", scriptPath, err) - os.Exit(1) - } - - // Compile script to bytecode - bytecode, err := state.CompileBytecode(string(scriptContent), scriptPath) - if err != nil { - fmt.Fprintf(os.Stderr, "Error compiling '%s': %v\n", scriptPath, err) - os.Exit(1) - } - - // Execute the compiled bytecode - if err := state.LoadAndRunBytecode(bytecode, scriptPath); err != nil { - fmt.Fprintf(os.Stderr, "Error executing '%s': %v\n", scriptPath, err) + // Execute the script + if err := luaState.ExecuteFile(scriptPath); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } diff --git a/state/state.go b/state/state.go new file mode 100644 index 0000000..3b237a2 --- /dev/null +++ b/state/state.go @@ -0,0 +1,266 @@ +package state + +import ( + "fmt" + "os" + "path/filepath" + + "Moonshark/modules" + + luajit "git.sharkk.net/Sky/LuaJIT-to-Go" +) + +// State wraps luajit.State with enhanced functionality +type State struct { + *luajit.State + initialized bool + isWorker bool +} + +// Config holds state initialization options +type Config struct { + OpenLibs bool + InstallModules bool + ScriptDir string + IsWorker bool +} + +// DefaultConfig returns default configuration +func DefaultConfig() Config { + return Config{ + OpenLibs: true, + InstallModules: true, + IsWorker: false, + } +} + +// New creates a new enhanced Lua state +func New(config ...Config) (*State, error) { + cfg := DefaultConfig() + if len(config) > 0 { + cfg = config[0] + } + + // Create base state + baseState := luajit.New(cfg.OpenLibs) + if baseState == nil { + return nil, fmt.Errorf("failed to create Lua state") + } + + state := &State{ + State: baseState, + isWorker: cfg.IsWorker, + } + + // Set worker global flag if this is a worker state + if cfg.IsWorker { + state.PushBoolean(true) + state.SetGlobal("__IS_WORKER") + } + + // Install module system if requested + if cfg.InstallModules { + if err := state.initializeModules(); err != nil { + state.Close() + return nil, fmt.Errorf("failed to install modules: %w", err) + } + } + + // Set script directory if provided + if cfg.ScriptDir != "" { + if err := state.SetScriptDirectory(cfg.ScriptDir); err != nil { + state.Close() + return nil, fmt.Errorf("failed to set script directory: %w", err) + } + } + + state.initialized = true + return state, nil +} + +// NewFromScript creates a state configured for a specific script file +func NewFromScript(scriptPath string, config ...Config) (*State, error) { + cfg := DefaultConfig() + if len(config) > 0 { + cfg = config[0] + } + + // Set script directory from file path + scriptDir := filepath.Dir(scriptPath) + absScriptDir, err := filepath.Abs(scriptDir) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %w", err) + } + cfg.ScriptDir = absScriptDir + + return New(cfg) +} + +// NewWorker creates a new worker state with __IS_WORKER flag set +func NewWorker(config ...Config) (*State, error) { + cfg := DefaultConfig() + if len(config) > 0 { + cfg = config[0] + } + cfg.IsWorker = true + + return New(cfg) +} + +// NewWorkerFromScript creates a worker state configured for a specific script +func NewWorkerFromScript(scriptPath string, config ...Config) (*State, error) { + cfg := DefaultConfig() + if len(config) > 0 { + cfg = config[0] + } + cfg.IsWorker = true + + scriptDir := filepath.Dir(scriptPath) + absScriptDir, err := filepath.Abs(scriptDir) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %w", err) + } + cfg.ScriptDir = absScriptDir + + return New(cfg) +} + +// initializeModules sets up the module system +func (s *State) initializeModules() error { + // Initialize global registry if needed + if modules.Global == nil { + if err := modules.Initialize(); err != nil { + return err + } + } + + return modules.Global.InstallInState(s.State) +} + +// SetScriptDirectory adds a directory to Lua's package.path +func (s *State) SetScriptDirectory(dir string) error { + packagePath := filepath.Join(dir, "?.lua") + return s.AddPackagePath(packagePath) +} + +// ExecuteFile compiles and runs a Lua script file +func (s *State) ExecuteFile(scriptPath string) error { + // Check if file exists + if _, err := os.Stat(scriptPath); os.IsNotExist(err) { + return fmt.Errorf("script file '%s' not found", scriptPath) + } + + // Read script content + scriptContent, err := os.ReadFile(scriptPath) + if err != nil { + return fmt.Errorf("failed to read script file '%s': %w", scriptPath, err) + } + + return s.ExecuteString(string(scriptContent), scriptPath) +} + +// ExecuteString compiles and runs Lua code +func (s *State) ExecuteString(code, name string) error { + // Use bytecode compilation for better performance + bytecode, err := s.CompileBytecode(code, name) + if err != nil { + return fmt.Errorf("compilation error in '%s': %w", name, err) + } + + if err := s.LoadAndRunBytecode(bytecode, name); err != nil { + return fmt.Errorf("execution error in '%s': %w", name, err) + } + + return nil +} + +// ExecuteFileWithResults executes a script and returns results +func (s *State) ExecuteFileWithResults(scriptPath string) ([]any, error) { + if _, err := os.Stat(scriptPath); os.IsNotExist(err) { + return nil, fmt.Errorf("script file '%s' not found", scriptPath) + } + + scriptContent, err := os.ReadFile(scriptPath) + if err != nil { + return nil, fmt.Errorf("failed to read script file '%s': %w", scriptPath, err) + } + + return s.ExecuteStringWithResults(string(scriptContent), scriptPath) +} + +// ExecuteStringWithResults executes code and returns all results +func (s *State) ExecuteStringWithResults(code, name string) ([]any, error) { + baseTop := s.GetTop() + defer s.SetTop(baseTop) + + nresults, err := s.Execute(code) + if err != nil { + return nil, fmt.Errorf("execution error in '%s': %w", name, err) + } + + if nresults == 0 { + return nil, nil + } + + results := make([]any, nresults) + for i := 0; i < nresults; i++ { + val, err := s.ToValue(baseTop + i + 1) + if err != nil { + results[i] = nil + } else { + results[i] = val + } + } + + return results, nil +} + +// IsInitialized returns whether the state has been properly initialized +func (s *State) IsInitialized() bool { + return s.initialized +} + +// IsWorker returns whether this state is a worker state +func (s *State) IsWorker() bool { + return s.isWorker +} + +// Close cleans up the state and releases resources +func (s *State) Close() { + if s.State != nil { + s.Cleanup() + s.State.Close() + s.State = nil + } + s.initialized = false +} + +// Quick creates a minimal state for one-off script execution +func Quick() (*State, error) { + return New(Config{ + OpenLibs: true, + InstallModules: true, + }) +} + +// QuickExecute creates a state, executes code, and cleans up +func QuickExecute(code, name string) error { + state, err := Quick() + if err != nil { + return err + } + defer state.Close() + + return state.ExecuteString(code, name) +} + +// QuickExecuteWithResults executes code and returns results +func QuickExecuteWithResults(code, name string) ([]any, error) { + state, err := Quick() + if err != nil { + return nil, err + } + defer state.Close() + + return state.ExecuteStringWithResults(code, name) +}