package compiler import ( "bytes" "strings" "go.starlark.net/lib/math" "go.starlark.net/starlark" ) // executeScript runs a Starlark script and returns the output lines func executeScript(scriptLines []string, ctx *CompilerContext) ([]string, error) { // Join script lines scriptText := strings.Join(scriptLines, "\n") // Expand |varname| -> actual variable names scriptText = expandVariables(scriptText, ctx) // Wrap in function (Starlark requires control flow inside functions) wrappedScript := "def _main():\n" for _, line := range strings.Split(scriptText, "\n") { wrappedScript += " " + line + "\n" } wrappedScript += "_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 // Predeclared functions (math module) predeclared := starlark.StringDict{ "math": math.Module, } // Execute _, err := starlark.ExecFile(thread, "script.star", wrappedScript, predeclared) if err != nil { return nil, err } // 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 }