Local variable expansion when calling macro
This commit is contained in:
parent
b614bcb043
commit
4f4df41c18
2 changed files with 221 additions and 0 deletions
|
|
@ -423,6 +423,222 @@ func TestExecuteMacro_StringParameter(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestExecuteMacro_LocalVariableExpansion(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := NewCompilerContext(pragma)
|
||||
|
||||
// Add a local variable in function scope "testfunc"
|
||||
ctx.SymbolTable.AddVar("myvar", "testfunc", KindByte, 0)
|
||||
|
||||
// Enter the function scope by declaring the function
|
||||
_, err := ctx.FunctionHandler.HandleFuncDecl(makeLine("FUNC testfunc"))
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
|
||||
// Register a macro that uses |%s| pattern - the ACTUAL use case
|
||||
// The macro parameter 'varname' receives "myvar", then |%s| % varname
|
||||
// produces |myvar| in the output, which should then be expanded
|
||||
ctx.ScriptMacros["load_var"] = &ScriptMacro{
|
||||
Name: "load_var",
|
||||
Params: []string{"varname"},
|
||||
Body: []string{
|
||||
"print(' lda |%s|' % varname)",
|
||||
},
|
||||
}
|
||||
|
||||
// Execute macro with "myvar" as argument - should expand |myvar| to testfunc_myvar
|
||||
output, err := ExecuteMacro("load_var", []string{"myvar"}, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteMacro failed: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 1 {
|
||||
t.Fatalf("expected 1 output line, got %d: %v", len(output), output)
|
||||
}
|
||||
|
||||
expected := " lda testfunc_myvar"
|
||||
if output[0] != expected {
|
||||
t.Errorf("expected %q, got %q", expected, output[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteMacro_LocalVariableExpansion_MultipleVars(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := NewCompilerContext(pragma)
|
||||
|
||||
// Add local variables in function scope
|
||||
ctx.SymbolTable.AddVar("color_index", "myfunc", KindByte, 0)
|
||||
ctx.SymbolTable.AddVar("row_color", "myfunc", KindWord, 0)
|
||||
|
||||
// Add a global table (no function scope)
|
||||
ctx.SymbolTable.AddVar("scroll_color_table", "", KindWord, 0)
|
||||
|
||||
// Enter the function scope
|
||||
_, err := ctx.FunctionHandler.HandleFuncDecl(makeLine("FUNC myfunc"))
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
|
||||
// Register a macro using |%s| pattern - matches actual usage:
|
||||
// SCRIPT MACRO table_lookup(table, index, dest)
|
||||
// print(" ldx |%s|" % index)
|
||||
// print(" lda %s,x" % table)
|
||||
// print(" sta |%s|" % dest)
|
||||
// ENDSCRIPT
|
||||
ctx.ScriptMacros["table_lookup"] = &ScriptMacro{
|
||||
Name: "table_lookup",
|
||||
Params: []string{"table", "index", "dest"},
|
||||
Body: []string{
|
||||
"print(' ldx |%s|' % index)",
|
||||
"print(' lda %s,x' % table)",
|
||||
"print(' sta |%s|' % dest)",
|
||||
},
|
||||
}
|
||||
|
||||
// Execute macro with actual variable names as arguments
|
||||
output, err := ExecuteMacro("table_lookup", []string{"scroll_color_table", "color_index", "row_color"}, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteMacro failed: %v", err)
|
||||
}
|
||||
|
||||
expectedLines := []string{
|
||||
" ldx myfunc_color_index", // local var expanded with scope
|
||||
" lda scroll_color_table,x", // global var (no pipes) stays as-is
|
||||
" sta myfunc_row_color", // local var expanded with scope
|
||||
}
|
||||
|
||||
if len(output) != len(expectedLines) {
|
||||
t.Fatalf("expected %d output lines, got %d: %v", len(expectedLines), len(output), output)
|
||||
}
|
||||
|
||||
for i, expected := range expectedLines {
|
||||
if output[i] != expected {
|
||||
t.Errorf("line %d: expected %q, got %q", i, expected, output[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteScript_LocalVariableExpansion(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := NewCompilerContext(pragma)
|
||||
|
||||
// Add a local variable in function scope
|
||||
ctx.SymbolTable.AddVar("counter", "loopfunc", KindByte, 0)
|
||||
|
||||
// Enter the function scope
|
||||
_, err := ctx.FunctionHandler.HandleFuncDecl(makeLine("FUNC loopfunc"))
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
|
||||
// Script that uses |varname| syntax
|
||||
scriptLines := []string{
|
||||
"print(' inc |counter|')",
|
||||
}
|
||||
|
||||
output, err := executeScript(scriptLines, ctx, false)
|
||||
if err != nil {
|
||||
t.Fatalf("executeScript failed: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 1 {
|
||||
t.Fatalf("expected 1 output line, got %d: %v", len(output), output)
|
||||
}
|
||||
|
||||
expected := " inc loopfunc_counter"
|
||||
if output[0] != expected {
|
||||
t.Errorf("expected %q, got %q", expected, output[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteScript_Library_GlobalVariableExpansion(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := NewCompilerContext(pragma)
|
||||
|
||||
// Add a global variable
|
||||
ctx.SymbolTable.AddVar("global_counter", "", KindByte, 0)
|
||||
|
||||
// Library script that uses |varname| syntax at global scope
|
||||
// Should expand to the global name (unchanged since it's already global)
|
||||
libraryLines := []string{
|
||||
"def inc_global():",
|
||||
" print(' inc |global_counter|')",
|
||||
}
|
||||
|
||||
_, err := executeScript(libraryLines, ctx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("library script failed: %v", err)
|
||||
}
|
||||
|
||||
// Now call the library function from a regular script
|
||||
scriptLines := []string{
|
||||
"inc_global()",
|
||||
}
|
||||
|
||||
output, err := executeScript(scriptLines, ctx, false)
|
||||
if err != nil {
|
||||
t.Fatalf("executeScript failed: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 1 {
|
||||
t.Fatalf("expected 1 output line, got %d: %v", len(output), output)
|
||||
}
|
||||
|
||||
// Global variable should stay as-is (no function prefix)
|
||||
expected := " inc global_counter"
|
||||
if output[0] != expected {
|
||||
t.Errorf("expected %q, got %q", expected, output[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteScript_Library_VariableExpansionAtDefinitionTime(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := NewCompilerContext(pragma)
|
||||
|
||||
// Add a local variable in a function scope
|
||||
ctx.SymbolTable.AddVar("local_var", "caller", KindByte, 0)
|
||||
|
||||
// Library is defined at GLOBAL scope (not inside any function)
|
||||
// So |varname| expansion happens at global scope during library definition
|
||||
libraryLines := []string{
|
||||
"def use_local():",
|
||||
" print(' lda |local_var|')", // This expands at library definition time
|
||||
}
|
||||
|
||||
// Library defined at global scope - |local_var| won't find caller's local
|
||||
_, err := executeScript(libraryLines, ctx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("library script failed: %v", err)
|
||||
}
|
||||
|
||||
// Now enter the function scope and call the library function
|
||||
_, err = ctx.FunctionHandler.HandleFuncDecl(makeLine("FUNC caller"))
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
|
||||
scriptLines := []string{
|
||||
"use_local()",
|
||||
}
|
||||
|
||||
output, err := executeScript(scriptLines, ctx, false)
|
||||
if err != nil {
|
||||
t.Fatalf("executeScript failed: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 1 {
|
||||
t.Fatalf("expected 1 output line, got %d: %v", len(output), output)
|
||||
}
|
||||
|
||||
// Variable expansion happened at library definition time (global scope),
|
||||
// so local_var was NOT found and stays as literal "local_var"
|
||||
expected := " lda local_var"
|
||||
if output[0] != expected {
|
||||
t.Errorf("expected %q, got %q (library expands |vars| at definition time, not call time)", expected, output[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMacroInvocation(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
|
|
|||
|
|
@ -164,6 +164,11 @@ func ExecuteMacro(macroName string, args []string, ctx *CompilerContext) ([]stri
|
|||
if outputStr == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Expand |varname| -> actual variable names in the OUTPUT
|
||||
// This happens at call site, so local variables are resolved using caller's scope
|
||||
outputStr = expandVariables(outputStr, ctx)
|
||||
|
||||
return strings.Split(strings.TrimRight(outputStr, "\n"), "\n"), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue