package compiler import ( "bytes" "strings" "go.starlark.net/lib/math" "go.starlark.net/starlark" ) // executeScript runs a Starlark script and returns the output lines. // If isLibrary is true, the script is executed at top level (no _main wrapper) // and resulting globals are persisted to ctx.ScriptLibraryGlobals. func executeScript(scriptLines []string, ctx *CompilerContext, isLibrary bool) ([]string, error) { // Join script lines scriptText := strings.Join(scriptLines, "\n") // Expand |varname| -> actual variable names scriptText = expandVariables(scriptText, ctx) var finalScript string if isLibrary { // LIBRARY: execute at top level so defs become globals finalScript = scriptText } else { // Regular SCRIPT: wrap in function (Starlark requires control flow inside functions) finalScript = "def _main():\n" for _, line := range strings.Split(scriptText, "\n") { finalScript += " " + line + "\n" } finalScript += "_main()\n" } // Capture print output var output bytes.Buffer thread := &starlark.Thread{ Print: func(_ *starlark.Thread, msg string) { output.WriteString(msg) output.WriteString("\n") }, } // Set execution limit (prevent infinite loops) thread.SetMaxExecutionSteps(1000000) // 1M steps // Build predeclared: math module + library globals predeclared := starlark.StringDict{ "math": math.Module, } for k, v := range ctx.ScriptLibraryGlobals { predeclared[k] = v } // Execute globals, err := starlark.ExecFile(thread, "script.star", finalScript, predeclared) if err != nil { return nil, err } // For LIBRARY: persist new globals (functions, variables defined at top level) if isLibrary { for k, v := range globals { ctx.ScriptLibraryGlobals[k] = v } } // Split output into lines for assembly outputStr := output.String() if outputStr == "" { return []string{}, nil } lines := strings.Split(strings.TrimRight(outputStr, "\n"), "\n") return lines, nil } // expandVariables replaces |varname| with expanded variable names from symbol table func expandVariables(text string, ctx *CompilerContext) string { result := text for { start := strings.IndexByte(result, '|') if start == -1 { break } end := strings.IndexByte(result[start+1:], '|') if end == -1 { break // unclosed, let script fail } end += start + 1 varName := result[start+1 : end] expandedName := ctx.SymbolTable.ExpandName(varName, ctx.CurrentScope()) result = result[:start] + expandedName + result[end+1:] } return result }