Added SCRIPT LIBRARY block for reusable StarLark code.
This commit is contained in:
parent
617f67ecb5
commit
c6b67f8044
9 changed files with 448 additions and 39 deletions
20
examples/script_library_demo/cm.sh
Executable file
20
examples/script_library_demo/cm.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Define filename as variable
|
||||||
|
PROGNAME="script_library_demo"
|
||||||
|
# Only set C65LIBPATH if not already defined
|
||||||
|
if [ -z "$C65LIBPATH" ]; then
|
||||||
|
export C65LIBPATH=$(readlink -f "../../lib")
|
||||||
|
fi
|
||||||
|
# Compile
|
||||||
|
c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Compilation terminated"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo assemble.
|
||||||
|
acme ${PROGNAME}.s
|
||||||
|
if [ -f ${PROGNAME}.prg ]; then
|
||||||
|
rm ${PROGNAME}.prg
|
||||||
|
fi
|
||||||
|
# main.bin ${PROGNAME}.prg
|
||||||
|
mv main.bin main.prg
|
||||||
85
examples/script_library_demo/script_library_demo.c65
Normal file
85
examples/script_library_demo/script_library_demo.c65
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// SCRIPT LIBRARY Demo
|
||||||
|
// Demonstrates reusable Starlark functions defined in
|
||||||
|
// SCRIPT LIBRARY blocks and called from SCRIPT blocks
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
|
||||||
|
#INCLUDE <c64start.c65>
|
||||||
|
|
||||||
|
GOTO start
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// SCRIPT LIBRARY: Define reusable code generation functions
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
SCRIPT LIBRARY
|
||||||
|
def emit_nops(count):
|
||||||
|
for i in range(count):
|
||||||
|
print(" nop")
|
||||||
|
|
||||||
|
def emit_delay(cycles):
|
||||||
|
# 4 cycles per iteration (2x nop)
|
||||||
|
for i in range(cycles // 4):
|
||||||
|
print(" nop")
|
||||||
|
print(" nop")
|
||||||
|
remainder = cycles % 4
|
||||||
|
if remainder >= 3:
|
||||||
|
print(" bit $ea")
|
||||||
|
remainder -= 3
|
||||||
|
# 2 cycles per nop
|
||||||
|
for i in range(remainder // 2):
|
||||||
|
print(" nop")
|
||||||
|
ENDSCRIPT
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Second SCRIPT LIBRARY: Functions accumulate
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
SCRIPT LIBRARY
|
||||||
|
def emit_border_flash(color1, color2):
|
||||||
|
print(" lda #%d" % color1)
|
||||||
|
print(" sta $d020")
|
||||||
|
print(" lda #%d" % color2)
|
||||||
|
print(" sta $d020")
|
||||||
|
|
||||||
|
def emit_load_store(value, addr):
|
||||||
|
print(" lda #%d" % value)
|
||||||
|
print(" sta %d" % addr)
|
||||||
|
ENDSCRIPT
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Main code using library functions
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
LABEL start
|
||||||
|
|
||||||
|
// Use emit_nops from first library
|
||||||
|
SCRIPT
|
||||||
|
print("; 5 nops from library")
|
||||||
|
emit_nops(5)
|
||||||
|
ENDSCRIPT
|
||||||
|
|
||||||
|
// Use emit_delay from first library
|
||||||
|
SCRIPT
|
||||||
|
print("; 10 cycle delay")
|
||||||
|
emit_delay(10)
|
||||||
|
ENDSCRIPT
|
||||||
|
|
||||||
|
// Use emit_border_flash from second library
|
||||||
|
SCRIPT
|
||||||
|
print("; border flash red/blue")
|
||||||
|
emit_border_flash(2, 6)
|
||||||
|
ENDSCRIPT
|
||||||
|
|
||||||
|
// Combine multiple library functions
|
||||||
|
SCRIPT
|
||||||
|
print("; combined: delay + flash + nops")
|
||||||
|
emit_delay(8)
|
||||||
|
emit_border_flash(0, 1)
|
||||||
|
emit_nops(3)
|
||||||
|
ENDSCRIPT
|
||||||
|
|
||||||
|
// Use emit_load_store
|
||||||
|
SCRIPT
|
||||||
|
print("; load/store to border")
|
||||||
|
emit_load_store(5, 0xd020)
|
||||||
|
ENDSCRIPT
|
||||||
|
|
||||||
|
SUBEND
|
||||||
1
examples/script_library_demo/start_in_vice.sh
Normal file
1
examples/script_library_demo/start_in_vice.sh
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
x64 -autostartprgmode 1 main.prg
|
||||||
|
|
@ -37,19 +37,24 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
||||||
var codeOutput []string
|
var codeOutput []string
|
||||||
var lastKind = preproc.Source
|
var lastKind = preproc.Source
|
||||||
var scriptBuffer []string
|
var scriptBuffer []string
|
||||||
|
var scriptIsLibrary bool
|
||||||
|
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
// Detect kind transitions and emit markers
|
// Detect kind transitions and emit markers
|
||||||
if line.Kind != lastKind {
|
if line.Kind != lastKind {
|
||||||
// Execute and close previous Script block
|
// Execute and close previous Script or ScriptLibrary block
|
||||||
if lastKind == preproc.Script {
|
if lastKind == preproc.Script || lastKind == preproc.ScriptLibrary {
|
||||||
scriptOutput, err := executeScript(scriptBuffer, c.ctx)
|
scriptOutput, err := executeScript(scriptBuffer, c.ctx, scriptIsLibrary)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("script execution failed: %w", err)
|
return nil, fmt.Errorf("script execution failed: %w", err)
|
||||||
}
|
}
|
||||||
codeOutput = append(codeOutput, scriptOutput...)
|
codeOutput = append(codeOutput, scriptOutput...)
|
||||||
scriptBuffer = nil
|
scriptBuffer = nil
|
||||||
codeOutput = append(codeOutput, "; ENDSCRIPT")
|
if scriptIsLibrary {
|
||||||
|
codeOutput = append(codeOutput, "; ENDSCRIPT LIBRARY")
|
||||||
|
} else {
|
||||||
|
codeOutput = append(codeOutput, "; ENDSCRIPT")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close previous Assembler block
|
// Close previous Assembler block
|
||||||
|
|
@ -62,6 +67,10 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
||||||
codeOutput = append(codeOutput, "; ASM")
|
codeOutput = append(codeOutput, "; ASM")
|
||||||
} else if line.Kind == preproc.Script {
|
} else if line.Kind == preproc.Script {
|
||||||
codeOutput = append(codeOutput, "; SCRIPT")
|
codeOutput = append(codeOutput, "; SCRIPT")
|
||||||
|
scriptIsLibrary = false
|
||||||
|
} else if line.Kind == preproc.ScriptLibrary {
|
||||||
|
codeOutput = append(codeOutput, "; SCRIPT LIBRARY")
|
||||||
|
scriptIsLibrary = true
|
||||||
}
|
}
|
||||||
|
|
||||||
lastKind = line.Kind
|
lastKind = line.Kind
|
||||||
|
|
@ -89,7 +98,7 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
||||||
text = text[:start] + expandedName + text[end+1:]
|
text = text[:start] + expandedName + text[end+1:]
|
||||||
}
|
}
|
||||||
codeOutput = append(codeOutput, text)
|
codeOutput = append(codeOutput, text)
|
||||||
} else if line.Kind == preproc.Script {
|
} else if line.Kind == preproc.Script || line.Kind == preproc.ScriptLibrary {
|
||||||
// Collect script lines for execution
|
// Collect script lines for execution
|
||||||
scriptBuffer = append(scriptBuffer, line.Text)
|
scriptBuffer = append(scriptBuffer, line.Text)
|
||||||
}
|
}
|
||||||
|
|
@ -127,11 +136,11 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
||||||
|
|
||||||
// Close any open block
|
// Close any open block
|
||||||
if lastKind == preproc.Assembler {
|
if lastKind == preproc.Assembler {
|
||||||
//codeOutput = append(codeOutput, "; ENDASM")
|
|
||||||
return nil, fmt.Errorf("Unclosed ASM block.")
|
return nil, fmt.Errorf("Unclosed ASM block.")
|
||||||
} else if lastKind == preproc.Script {
|
} else if lastKind == preproc.Script {
|
||||||
//codeOutput = append(codeOutput, "; ENDSCRIPT")
|
|
||||||
return nil, fmt.Errorf("Unclosed SCRIPT block.")
|
return nil, fmt.Errorf("Unclosed SCRIPT block.")
|
||||||
|
} else if lastKind == preproc.ScriptLibrary {
|
||||||
|
return nil, fmt.Errorf("Unclosed SCRIPT LIBRARY block.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Analyze for overlapping absolute addresses in function call chains
|
// Analyze for overlapping absolute addresses in function call chains
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,9 @@ func TestCompilerContext(t *testing.T) {
|
||||||
if ctx.Pragma == nil {
|
if ctx.Pragma == nil {
|
||||||
t.Error("Pragma not initialized")
|
t.Error("Pragma not initialized")
|
||||||
}
|
}
|
||||||
|
if ctx.ScriptLibraryGlobals == nil {
|
||||||
|
t.Error("ScriptLibraryGlobals not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
// Test CurrentScope
|
// Test CurrentScope
|
||||||
scope := ctx.CurrentScope()
|
scope := ctx.CurrentScope()
|
||||||
|
|
@ -146,3 +149,184 @@ func TestCompilerContext(t *testing.T) {
|
||||||
t.Errorf("Expected nil scope in global context, got %v", scope)
|
t.Errorf("Expected nil scope in global context, got %v", scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExecuteScript_BasicPrint(t *testing.T) {
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
ctx := NewCompilerContext(pragma)
|
||||||
|
|
||||||
|
scriptLines := []string{
|
||||||
|
"for i in range(3):",
|
||||||
|
" print(' nop')",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := executeScript(scriptLines, ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("executeScript failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) != 3 {
|
||||||
|
t.Fatalf("expected 3 output lines, got %d: %v", len(output), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, line := range output {
|
||||||
|
if line != " nop" {
|
||||||
|
t.Errorf("line %d: expected ' nop', got %q", i, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteScript_EmptyOutput(t *testing.T) {
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
ctx := NewCompilerContext(pragma)
|
||||||
|
|
||||||
|
scriptLines := []string{
|
||||||
|
"x = 1 + 1",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := executeScript(scriptLines, ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("executeScript failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) != 0 {
|
||||||
|
t.Errorf("expected 0 output lines, got %d: %v", len(output), output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteScript_Library_DefinesFunction(t *testing.T) {
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
ctx := NewCompilerContext(pragma)
|
||||||
|
|
||||||
|
// Define a function in library mode
|
||||||
|
libraryLines := []string{
|
||||||
|
"def emit_nops(count):",
|
||||||
|
" for i in range(count):",
|
||||||
|
" print(' nop')",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := executeScript(libraryLines, ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("library executeScript failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify function is in globals
|
||||||
|
if _, ok := ctx.ScriptLibraryGlobals["emit_nops"]; !ok {
|
||||||
|
t.Fatal("expected emit_nops to be defined in ScriptLibraryGlobals")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteScript_Library_FunctionCallableFromScript(t *testing.T) {
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
ctx := NewCompilerContext(pragma)
|
||||||
|
|
||||||
|
// First: define function in library
|
||||||
|
libraryLines := []string{
|
||||||
|
"def emit_nops(count):",
|
||||||
|
" for i in range(count):",
|
||||||
|
" print(' nop')",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := executeScript(libraryLines, ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("library executeScript failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second: call function from regular script
|
||||||
|
scriptLines := []string{
|
||||||
|
"emit_nops(2)",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := executeScript(scriptLines, ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("script executeScript failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) != 2 {
|
||||||
|
t.Fatalf("expected 2 output lines, got %d: %v", len(output), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, line := range output {
|
||||||
|
if line != " nop" {
|
||||||
|
t.Errorf("line %d: expected ' nop', got %q", i, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteScript_MultipleLibraries_Accumulate(t *testing.T) {
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
ctx := NewCompilerContext(pragma)
|
||||||
|
|
||||||
|
// First library: define func_a
|
||||||
|
lib1 := []string{
|
||||||
|
"def func_a():",
|
||||||
|
" print(' ; from a')",
|
||||||
|
}
|
||||||
|
_, err := executeScript(lib1, ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("lib1 failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second library: define func_b (should still have func_a)
|
||||||
|
lib2 := []string{
|
||||||
|
"def func_b():",
|
||||||
|
" print(' ; from b')",
|
||||||
|
}
|
||||||
|
_, err = executeScript(lib2, ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("lib2 failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both functions should be available
|
||||||
|
if _, ok := ctx.ScriptLibraryGlobals["func_a"]; !ok {
|
||||||
|
t.Error("func_a missing after second library")
|
||||||
|
}
|
||||||
|
if _, ok := ctx.ScriptLibraryGlobals["func_b"]; !ok {
|
||||||
|
t.Error("func_b missing after second library")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call both from a script
|
||||||
|
scriptLines := []string{
|
||||||
|
"func_a()",
|
||||||
|
"func_b()",
|
||||||
|
}
|
||||||
|
output, err := executeScript(scriptLines, ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("script failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) != 2 {
|
||||||
|
t.Fatalf("expected 2 lines, got %d: %v", len(output), output)
|
||||||
|
}
|
||||||
|
if output[0] != " ; from a" {
|
||||||
|
t.Errorf("expected ' ; from a', got %q", output[0])
|
||||||
|
}
|
||||||
|
if output[1] != " ; from b" {
|
||||||
|
t.Errorf("expected ' ; from b', got %q", output[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteScript_RegularScript_DoesNotPersist(t *testing.T) {
|
||||||
|
pragma := preproc.NewPragma()
|
||||||
|
ctx := NewCompilerContext(pragma)
|
||||||
|
|
||||||
|
// Define function in regular script (not library)
|
||||||
|
scriptLines := []string{
|
||||||
|
"def local_func():",
|
||||||
|
" print('hello')",
|
||||||
|
"local_func()",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := executeScript(scriptLines, ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("script failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) != 1 || output[0] != "hello" {
|
||||||
|
t.Errorf("unexpected output: %v", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function should NOT be in globals (it was in regular script)
|
||||||
|
if _, ok := ctx.ScriptLibraryGlobals["local_func"]; ok {
|
||||||
|
t.Error("local_func should not persist from regular script")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"c65gm/internal/preproc"
|
"c65gm/internal/preproc"
|
||||||
|
|
||||||
|
"go.starlark.net/starlark"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompilerContext holds all shared resources needed by commands during compilation
|
// CompilerContext holds all shared resources needed by commands during compilation
|
||||||
|
|
@ -26,6 +28,9 @@ type CompilerContext struct {
|
||||||
|
|
||||||
// Pragma access for per-line pragma lookup
|
// Pragma access for per-line pragma lookup
|
||||||
Pragma *preproc.Pragma
|
Pragma *preproc.Pragma
|
||||||
|
|
||||||
|
// ScriptLibraryGlobals holds persisted Starlark globals from SCRIPT LIBRARY blocks
|
||||||
|
ScriptLibraryGlobals starlark.StringDict
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCompilerContext creates a new compiler context with initialized resources
|
// NewCompilerContext creates a new compiler context with initialized resources
|
||||||
|
|
@ -35,16 +40,17 @@ func NewCompilerContext(pragma *preproc.Pragma) *CompilerContext {
|
||||||
generalStack := NewLabelStack("_L")
|
generalStack := NewLabelStack("_L")
|
||||||
|
|
||||||
ctx := &CompilerContext{
|
ctx := &CompilerContext{
|
||||||
SymbolTable: symTable,
|
SymbolTable: symTable,
|
||||||
ConstStrHandler: constStrHandler,
|
ConstStrHandler: constStrHandler,
|
||||||
LoopStartStack: NewLabelStack("_LOOPSTART"),
|
LoopStartStack: NewLabelStack("_LOOPSTART"),
|
||||||
LoopEndStack: NewLabelStack("_LOOPEND"),
|
LoopEndStack: NewLabelStack("_LOOPEND"),
|
||||||
IfStack: NewLabelStack("_I"),
|
IfStack: NewLabelStack("_I"),
|
||||||
GeneralStack: generalStack,
|
GeneralStack: generalStack,
|
||||||
ForStack: NewForStack(),
|
ForStack: NewForStack(),
|
||||||
SwitchStack: NewSwitchStack(),
|
SwitchStack: NewSwitchStack(),
|
||||||
CaseSkipStack: NewLabelStack("_SKIPCASE"),
|
CaseSkipStack: NewLabelStack("_SKIPCASE"),
|
||||||
Pragma: pragma,
|
Pragma: pragma,
|
||||||
|
ScriptLibraryGlobals: make(starlark.StringDict),
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionHandler needs references to other components
|
// FunctionHandler needs references to other components
|
||||||
|
|
|
||||||
|
|
@ -8,20 +8,28 @@ import (
|
||||||
"go.starlark.net/starlark"
|
"go.starlark.net/starlark"
|
||||||
)
|
)
|
||||||
|
|
||||||
// executeScript runs a Starlark script and returns the output lines
|
// executeScript runs a Starlark script and returns the output lines.
|
||||||
func executeScript(scriptLines []string, ctx *CompilerContext) ([]string, error) {
|
// 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
|
// Join script lines
|
||||||
scriptText := strings.Join(scriptLines, "\n")
|
scriptText := strings.Join(scriptLines, "\n")
|
||||||
|
|
||||||
// Expand |varname| -> actual variable names
|
// Expand |varname| -> actual variable names
|
||||||
scriptText = expandVariables(scriptText, ctx)
|
scriptText = expandVariables(scriptText, ctx)
|
||||||
|
|
||||||
// Wrap in function (Starlark requires control flow inside functions)
|
var finalScript string
|
||||||
wrappedScript := "def _main():\n"
|
if isLibrary {
|
||||||
for _, line := range strings.Split(scriptText, "\n") {
|
// LIBRARY: execute at top level so defs become globals
|
||||||
wrappedScript += " " + line + "\n"
|
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"
|
||||||
}
|
}
|
||||||
wrappedScript += "_main()\n"
|
|
||||||
|
|
||||||
// Capture print output
|
// Capture print output
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
|
|
@ -35,17 +43,27 @@ func executeScript(scriptLines []string, ctx *CompilerContext) ([]string, error)
|
||||||
// Set execution limit (prevent infinite loops)
|
// Set execution limit (prevent infinite loops)
|
||||||
thread.SetMaxExecutionSteps(1000000) // 1M steps
|
thread.SetMaxExecutionSteps(1000000) // 1M steps
|
||||||
|
|
||||||
// Predeclared functions (math module)
|
// Build predeclared: math module + library globals
|
||||||
predeclared := starlark.StringDict{
|
predeclared := starlark.StringDict{
|
||||||
"math": math.Module,
|
"math": math.Module,
|
||||||
}
|
}
|
||||||
|
for k, v := range ctx.ScriptLibraryGlobals {
|
||||||
|
predeclared[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
_, err := starlark.ExecFile(thread, "script.star", wrappedScript, predeclared)
|
globals, err := starlark.ExecFile(thread, "script.star", finalScript, predeclared)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Split output into lines for assembly
|
||||||
outputStr := output.String()
|
outputStr := output.String()
|
||||||
if outputStr == "" {
|
if outputStr == "" {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const (
|
||||||
Source LineKind = iota
|
Source LineKind = iota
|
||||||
Assembler
|
Assembler
|
||||||
Script
|
Script
|
||||||
|
ScriptLibrary
|
||||||
)
|
)
|
||||||
|
|
||||||
// Line represents one post-processed source line and its provenance.
|
// Line represents one post-processed source line and its provenance.
|
||||||
|
|
@ -46,12 +47,13 @@ func PreProcess(rootFilename string, reader ...FileReader) ([]Line, *Pragma, err
|
||||||
// -------------------- internal --------------------
|
// -------------------- internal --------------------
|
||||||
|
|
||||||
type preproc struct {
|
type preproc struct {
|
||||||
defs *DefineList // from definelist.go
|
defs *DefineList // from definelist.go
|
||||||
pragma *Pragma // pragma handler
|
pragma *Pragma // pragma handler
|
||||||
cond []bool // conditional stack; a line is active if all are true
|
cond []bool // conditional stack; a line is active if all are true
|
||||||
inAsm bool // true when inside ASM/ENDASM block
|
inAsm bool // true when inside ASM/ENDASM block
|
||||||
inScript bool // true when inside SCRIPT/ENDSCRIPT block
|
inScript bool // true when inside SCRIPT/ENDSCRIPT block
|
||||||
reader FileReader // file reader abstraction
|
inScriptLibrary bool // true when inside SCRIPT LIBRARY/ENDSCRIPT block
|
||||||
|
reader FileReader // file reader abstraction
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPreproc(reader FileReader) *preproc {
|
func newPreproc(reader FileReader) *preproc {
|
||||||
|
|
@ -116,15 +118,19 @@ func (p *preproc) run(root string) ([]Line, error) {
|
||||||
tokens := strings.Fields(raw)
|
tokens := strings.Fields(raw)
|
||||||
|
|
||||||
// ASM mode handling
|
// ASM mode handling
|
||||||
if !p.inAsm && !p.inScript {
|
if !p.inAsm && !p.inScript && !p.inScriptLibrary {
|
||||||
// Check for ASM entry
|
// Check for ASM entry
|
||||||
if includeSource && len(tokens) > 0 && tokens[0] == "ASM" {
|
if includeSource && len(tokens) > 0 && tokens[0] == "ASM" {
|
||||||
p.inAsm = true
|
p.inAsm = true
|
||||||
continue // don't emit ASM marker
|
continue // don't emit ASM marker
|
||||||
}
|
}
|
||||||
// Check for SCRIPT entry
|
// Check for SCRIPT entry (SCRIPT LIBRARY or plain SCRIPT)
|
||||||
if includeSource && len(tokens) > 0 && tokens[0] == "SCRIPT" {
|
if includeSource && len(tokens) > 0 && tokens[0] == "SCRIPT" {
|
||||||
p.inScript = true
|
if len(tokens) > 1 && tokens[1] == "LIBRARY" {
|
||||||
|
p.inScriptLibrary = true
|
||||||
|
} else {
|
||||||
|
p.inScript = true
|
||||||
|
}
|
||||||
continue // don't emit SCRIPT marker
|
continue // don't emit SCRIPT marker
|
||||||
}
|
}
|
||||||
} else if p.inAsm {
|
} else if p.inAsm {
|
||||||
|
|
@ -144,20 +150,26 @@ func (p *preproc) run(root string) ([]Line, error) {
|
||||||
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
} else if p.inScript {
|
} else if p.inScript || p.inScriptLibrary {
|
||||||
// We're in SCRIPT mode
|
// We're in SCRIPT or SCRIPT LIBRARY mode
|
||||||
// Check for ENDSCRIPT
|
// Check for ENDSCRIPT
|
||||||
if len(tokens) > 0 && tokens[0] == "ENDSCRIPT" {
|
if len(tokens) > 0 && tokens[0] == "ENDSCRIPT" {
|
||||||
p.inScript = false
|
p.inScript = false
|
||||||
|
p.inScriptLibrary = false
|
||||||
continue // don't emit ENDSCRIPT marker
|
continue // don't emit ENDSCRIPT marker
|
||||||
}
|
}
|
||||||
// Otherwise emit line verbatim as Script
|
// Determine the kind based on which mode we're in
|
||||||
|
kind := Script
|
||||||
|
if p.inScriptLibrary {
|
||||||
|
kind = ScriptLibrary
|
||||||
|
}
|
||||||
|
// Emit line verbatim with appropriate kind
|
||||||
out = append(out, Line{
|
out = append(out, Line{
|
||||||
RawText: raw,
|
RawText: raw,
|
||||||
Text: raw,
|
Text: raw,
|
||||||
Filename: currFrame.path,
|
Filename: currFrame.path,
|
||||||
LineNo: currFrame.line,
|
LineNo: currFrame.line,
|
||||||
Kind: Script,
|
Kind: kind,
|
||||||
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
PragmaSetIndex: p.pragma.GetCurrentPragmaSetIndex(),
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -947,6 +947,80 @@ func TestPreProcess_EmptyScriptBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreProcess_ScriptLibraryBlock(t *testing.T) {
|
||||||
|
files := map[string][]string{
|
||||||
|
"test.c65": {
|
||||||
|
"SCRIPT LIBRARY",
|
||||||
|
"def my_func():",
|
||||||
|
" print('nop')",
|
||||||
|
"ENDSCRIPT",
|
||||||
|
"NOP",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reader := NewMockFileReader(files)
|
||||||
|
lines, _, err := PreProcess("test.c65", reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PreProcess failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have 2 script lines + 1 source line
|
||||||
|
if len(lines) != 3 {
|
||||||
|
t.Fatalf("expected 3 lines, got %d", len(lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script library lines should have ScriptLibrary kind
|
||||||
|
if lines[0].Kind != ScriptLibrary {
|
||||||
|
t.Errorf("expected Kind=ScriptLibrary, got %v", lines[0].Kind)
|
||||||
|
}
|
||||||
|
if lines[0].Text != "def my_func():" {
|
||||||
|
t.Errorf("expected 'def my_func():', got %q", lines[0].Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lines[1].Kind != ScriptLibrary {
|
||||||
|
t.Errorf("expected Kind=ScriptLibrary, got %v", lines[1].Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source line after ENDSCRIPT
|
||||||
|
if lines[2].Kind != Source {
|
||||||
|
t.Errorf("expected Kind=Source, got %v", lines[2].Kind)
|
||||||
|
}
|
||||||
|
if lines[2].Text != "NOP" {
|
||||||
|
t.Errorf("expected 'NOP', got %q", lines[2].Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreProcess_ScriptVsScriptLibrary(t *testing.T) {
|
||||||
|
files := map[string][]string{
|
||||||
|
"test.c65": {
|
||||||
|
"SCRIPT LIBRARY",
|
||||||
|
"def foo(): pass",
|
||||||
|
"ENDSCRIPT",
|
||||||
|
"SCRIPT",
|
||||||
|
"foo()",
|
||||||
|
"ENDSCRIPT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reader := NewMockFileReader(files)
|
||||||
|
lines, _, err := PreProcess("test.c65", reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PreProcess failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lines) != 2 {
|
||||||
|
t.Fatalf("expected 2 lines, got %d", len(lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First line is from SCRIPT LIBRARY
|
||||||
|
if lines[0].Kind != ScriptLibrary {
|
||||||
|
t.Errorf("line 0: expected ScriptLibrary, got %v", lines[0].Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second line is from regular SCRIPT
|
||||||
|
if lines[1].Kind != Script {
|
||||||
|
t.Errorf("line 1: expected Script, got %v", lines[1].Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPreProcess_DollarEscapeExpansion(t *testing.T) {
|
func TestPreProcess_DollarEscapeExpansion(t *testing.T) {
|
||||||
files := map[string][]string{
|
files := map[string][]string{
|
||||||
"test.c65": {
|
"test.c65": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue