c65gm/internal/compiler/scriptexec.go

95 lines
2.5 KiB
Go

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
}