Merge pull request 'auto_remove_unused_funcs' (#4) from auto_remove_unused_funcs into main
Reviewed-on: #4
This commit is contained in:
commit
e5ee83d3dd
8 changed files with 213 additions and 51 deletions
46
AGENTS.md
46
AGENTS.md
|
|
@ -116,12 +116,33 @@ All file operations must be restricted to the project's source code and document
|
|||
3. Update documentation in appropriate .md files
|
||||
4. Consider adding examples in `examples/` directory
|
||||
|
||||
### Testing
|
||||
**IMPORTANT**: The agent cannot run tests. Provide these instructions to the user:
|
||||
### Testing and Rebuilding
|
||||
**IMPORTANT**: The agent cannot run tests or compile code. Provide these instructions to the user:
|
||||
|
||||
#### Testing:
|
||||
- Run all tests: `go test ./...`
|
||||
- Run specific package tests: `go test ./internal/compiler`
|
||||
- Test with verbose output: `go test -v ./...`
|
||||
|
||||
#### Rebuilding c65gm:
|
||||
When making changes to the compiler, ask the user to rebuild c65gm:
|
||||
```bash
|
||||
go build -o c65gm
|
||||
```
|
||||
This creates a fresh binary with your changes. The agent should:
|
||||
1. Make code changes
|
||||
2. Ask the user to rebuild c65gm: `go build -o c65gm`
|
||||
3. **Check file datetime**: Verify the c65gm binary was recently updated (use `ls -la c65gm` or similar)
|
||||
4. Ask the user to run tests: `go test ./...`
|
||||
5. Test the changes with example .c65 files
|
||||
|
||||
**IMPORTANT**: Always check the file datetime of the c65gm binary after asking for a rebuild. If the timestamp hasn't changed, the user may have forgotten to rebuild, or the build may have failed. Testing with an old version is wasteful and can lead to incorrect conclusions.
|
||||
|
||||
#### Compiling .c65 files:
|
||||
```bash
|
||||
C65LIBPATH=/app/lib ./c65gm -in input.c65 -out output.asm
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Expression evaluation**: Remember no operator precedence
|
||||
|
|
@ -165,3 +186,24 @@ acme -f cbm -o output.prg output.asm
|
|||
### Testing
|
||||
- `*_test.go` files throughout the codebase
|
||||
- `examples/`: Functional test programs
|
||||
|
||||
## Tool Notes
|
||||
|
||||
### Shell Environment
|
||||
- **Shell**: Only `/bin/sh` (BusyBox) is available, not bash
|
||||
- **The `bash` tool** in the interface runs commands through `/bin/sh` (BusyBox)
|
||||
- **BusyBox limitations**: Many GNU extensions are not available
|
||||
|
||||
### grep Limitations
|
||||
The environment uses BusyBox grep which doesn't support GNU grep extensions:
|
||||
- **NOT supported**: `--include="*.go"`, `--exclude="*.md"`, etc.
|
||||
- **Use instead**: `find /app -name "*.go" -type f -exec grep -l "pattern" {} \;`
|
||||
- **Example**: Instead of `grep -r "CheckUnusedFunctions" /app --include="*.go"`, use:
|
||||
```bash
|
||||
find /app -name "*.go" -type f -exec grep -l "CheckUnusedFunctions" {} \;
|
||||
```
|
||||
|
||||
### File Operations
|
||||
- Use `find` with `-exec` for pattern-based file searches
|
||||
- Use `grep` without GNU extensions for simple pattern matching
|
||||
- Prefer the `bash` tool with proper BusyBox-compatible commands
|
||||
|
|
@ -38,6 +38,8 @@ func (c *FendCommand) Interpret(line preproc.Line, _ *compiler.CompilerContext)
|
|||
}
|
||||
|
||||
func (c *FendCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
funcName := ctx.FunctionHandler.CurrentFunction()
|
||||
ctx.FunctionHandler.EndFunction()
|
||||
return []string{"\trts"}, nil
|
||||
// Return RTS followed by end marker
|
||||
return []string{"\trts", fmt.Sprintf("; @@FUNC_END %s", funcName)}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
|
|
@ -16,7 +17,7 @@ import (
|
|||
// FUNC name ( in:x out:y io:z ) # with direction modifiers
|
||||
// FUNC name ( {BYTE temp} out:result ) # with implicit declarations
|
||||
type FuncCommand struct {
|
||||
asmOutput []string
|
||||
funcName string
|
||||
}
|
||||
|
||||
func (c *FuncCommand) WillHandle(line preproc.Line) bool {
|
||||
|
|
@ -28,17 +29,22 @@ func (c *FuncCommand) WillHandle(line preproc.Line) bool {
|
|||
}
|
||||
|
||||
func (c *FuncCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
c.asmOutput = nil
|
||||
c.funcName = ""
|
||||
|
||||
asm, err := ctx.FunctionHandler.HandleFuncDecl(line)
|
||||
funcName, err := ctx.FunctionHandler.HandleFuncDecl(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.asmOutput = asm
|
||||
c.funcName = funcName
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FuncCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
return c.asmOutput, nil
|
||||
if c.funcName == "" {
|
||||
return nil, nil // No function name parsed
|
||||
}
|
||||
// Prepend marker before the function label
|
||||
marker := fmt.Sprintf("; @@FUNC_BEGIN %s", c.funcName)
|
||||
return []string{marker, c.funcName}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package compiler
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/preproc"
|
||||
|
|
@ -234,6 +235,9 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
|||
_, _ = fmt.Fprintf(os.Stderr, "%s\n", warning)
|
||||
}
|
||||
|
||||
// Remove unused functions with _P_REMOVE_UNUSED pragma
|
||||
codeOutput = c.removeUnusedFunctions(codeOutput)
|
||||
|
||||
// Assemble final output with headers and footers
|
||||
return c.assembleOutput(codeOutput), nil
|
||||
}
|
||||
|
|
@ -449,6 +453,71 @@ func findPipeOutsideStrings(line string, startFrom int) int {
|
|||
return -1
|
||||
}
|
||||
|
||||
// removeUnusedFunctions removes functions marked with _P_REMOVE_UNUSED pragma from assembly output
|
||||
func (c *Compiler) removeUnusedFunctions(codeLines []string) []string {
|
||||
toRemove := c.ctx.FunctionHandler.GetFunctionsToRemove()
|
||||
if len(toRemove) == 0 {
|
||||
return codeLines
|
||||
}
|
||||
|
||||
var result []string
|
||||
i := 0
|
||||
for i < len(codeLines) {
|
||||
line := codeLines[i]
|
||||
|
||||
// Check for function start marker
|
||||
if strings.HasPrefix(line, "; @@FUNC_BEGIN ") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 3 && toRemove[parts[2]] {
|
||||
funcName := parts[2]
|
||||
|
||||
// Find the function declaration to get file/line info
|
||||
var filename string
|
||||
var lineNo int
|
||||
for _, funcDecl := range c.ctx.FunctionHandler.functions {
|
||||
if funcDecl.Name == funcName {
|
||||
filename = funcDecl.Line.Filename
|
||||
lineNo = funcDecl.Line.LineNo
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Print info message to stdout
|
||||
if filename != "" {
|
||||
baseFilename := filepath.Base(filename)
|
||||
fmt.Printf("info:%s:%d:FUNC %s removed.\n", baseFilename, lineNo, funcName)
|
||||
} else {
|
||||
fmt.Printf("info:unknown:0:FUNC %s removed.\n", funcName)
|
||||
}
|
||||
|
||||
// Skip everything until matching @@FUNC_END
|
||||
foundEnd := false
|
||||
for i < len(codeLines) {
|
||||
if strings.HasPrefix(codeLines[i], "; @@FUNC_END ") {
|
||||
// Check if this is the exact function end marker
|
||||
parts := strings.Fields(codeLines[i])
|
||||
if len(parts) >= 3 && parts[2] == funcName {
|
||||
foundEnd = true
|
||||
break
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
// Skip the END marker line too if found
|
||||
if foundEnd && i < len(codeLines) {
|
||||
i++
|
||||
}
|
||||
// If we didn't find the end marker, we've reached end of file
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, line)
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// assembleOutput combines all generated sections into final assembly
|
||||
func (c *Compiler) assembleOutput(codeLines []string) []string {
|
||||
var output []string
|
||||
|
|
|
|||
|
|
@ -70,28 +70,29 @@ func NewFunctionHandler(st *SymbolTable, ls *LabelStack, csh *ConstantStringHand
|
|||
// HandleFuncDecl parses and processes a FUNC declaration
|
||||
// Syntax: FUNC name ( param1 param2 ... )
|
||||
// Or: FUNC name (void function)
|
||||
func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
||||
// Returns the function name
|
||||
func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) (string, error) {
|
||||
// Normalize parentheses and commas
|
||||
text := fixIntuitiveFuncs(line.Text)
|
||||
|
||||
params, err := parseParams(text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err)
|
||||
return "", fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err)
|
||||
}
|
||||
|
||||
if len(params) < 2 {
|
||||
return nil, fmt.Errorf("%s:%d: FUNC: expected at least function name", line.Filename, line.LineNo)
|
||||
return "", fmt.Errorf("%s:%d: FUNC: expected at least function name", line.Filename, line.LineNo)
|
||||
}
|
||||
|
||||
if strings.ToUpper(params[0]) != "FUNC" {
|
||||
return nil, fmt.Errorf("%s:%d: not a FUNC declaration", line.Filename, line.LineNo)
|
||||
return "", fmt.Errorf("%s:%d: not a FUNC declaration", line.Filename, line.LineNo)
|
||||
}
|
||||
|
||||
funcName := params[1]
|
||||
|
||||
// Check for redeclaration
|
||||
if fh.FuncExists(funcName) {
|
||||
return nil, fmt.Errorf("%s:%d: function %q already declared", line.Filename, line.LineNo, funcName)
|
||||
return "", fmt.Errorf("%s:%d: function %q already declared", line.Filename, line.LineNo, funcName)
|
||||
}
|
||||
|
||||
// Push function name to current function stack early
|
||||
|
|
@ -108,7 +109,7 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
|||
// FUNC name ( param1 param2 )
|
||||
if params[2] != "(" || params[len(params)-1] != ")" {
|
||||
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
|
||||
return nil, fmt.Errorf("%s:%d: FUNC: expected parentheses around parameters", line.Filename, line.LineNo)
|
||||
return "", fmt.Errorf("%s:%d: FUNC: expected parentheses around parameters", line.Filename, line.LineNo)
|
||||
}
|
||||
|
||||
// Extract params between ( and ) - need to handle {BYTE x} specially
|
||||
|
|
@ -116,14 +117,14 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
|||
paramSpecs, err := buildComplexParams(rawParamTokens)
|
||||
if err != nil {
|
||||
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
|
||||
return nil, fmt.Errorf("%s:%d: FUNC %s: %w", line.Filename, line.LineNo, funcName, err)
|
||||
return "", fmt.Errorf("%s:%d: FUNC %s: %w", line.Filename, line.LineNo, funcName, err)
|
||||
}
|
||||
|
||||
for _, spec := range paramSpecs {
|
||||
direction, varName, isImplicit, implicitDecl, err := parseParamSpec(spec)
|
||||
if err != nil {
|
||||
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
|
||||
return nil, fmt.Errorf("%s:%d: FUNC %s: %w", line.Filename, line.LineNo, funcName, err)
|
||||
return "", fmt.Errorf("%s:%d: FUNC %s: %w", line.Filename, line.LineNo, funcName, err)
|
||||
}
|
||||
|
||||
if isImplicit {
|
||||
|
|
@ -131,7 +132,7 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
|||
// Format: {BYTE varname} or {WORD varname}
|
||||
if err := fh.parseImplicitDecl(implicitDecl, funcName, line); err != nil {
|
||||
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
|
||||
return nil, fmt.Errorf("%s:%d: FUNC %s: implicit declaration: %w", line.Filename, line.LineNo, funcName, err)
|
||||
return "", fmt.Errorf("%s:%d: FUNC %s: implicit declaration: %w", line.Filename, line.LineNo, funcName, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,12 +140,12 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
|||
sym := fh.symTable.LookupWithoutUsage(varName, []string{funcName})
|
||||
if sym == nil {
|
||||
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
|
||||
return nil, fmt.Errorf("%s:%d: FUNC %s: parameter %q not declared", line.Filename, line.LineNo, funcName, varName)
|
||||
return "", fmt.Errorf("%s:%d: FUNC %s: parameter %q not declared", line.Filename, line.LineNo, funcName, varName)
|
||||
}
|
||||
|
||||
if sym.IsConst() {
|
||||
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
|
||||
return nil, fmt.Errorf("%s:%d: FUNC %s: parameter %q cannot be a constant", line.Filename, line.LineNo, funcName, varName)
|
||||
return "", fmt.Errorf("%s:%d: FUNC %s: parameter %q cannot be a constant", line.Filename, line.LineNo, funcName, varName)
|
||||
}
|
||||
|
||||
funcParams = append(funcParams, &FuncParam{
|
||||
|
|
@ -154,7 +155,7 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
|||
}
|
||||
} else {
|
||||
fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1]
|
||||
return nil, fmt.Errorf("%s:%d: FUNC: invalid syntax", line.Filename, line.LineNo)
|
||||
return "", fmt.Errorf("%s:%d: FUNC: invalid syntax", line.Filename, line.LineNo)
|
||||
}
|
||||
|
||||
// Store function declaration
|
||||
|
|
@ -167,8 +168,7 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
|||
// Record absolute addresses used by this function
|
||||
fh.recordAbsoluteAddresses(funcName, funcParams)
|
||||
|
||||
// Generate assembler label
|
||||
return []string{funcName}, nil
|
||||
return funcName, nil
|
||||
}
|
||||
|
||||
// recordAbsoluteAddresses stores which absolute addresses a function uses
|
||||
|
|
@ -1020,10 +1020,16 @@ func (fh *FunctionHandler) CheckUnusedFunctions() []string {
|
|||
// Check if pragma indicates we should ignore unused warnings for this function
|
||||
if fh.pragma != nil {
|
||||
pragmaSet := fh.pragma.GetPragmaSetByIndex(funcDecl.Line.PragmaSetIndex)
|
||||
// Skip warning if _P_IGNORE_UNUSED is enabled
|
||||
value := pragmaSet.GetPragma("_P_IGNORE_UNUSED")
|
||||
if value != "" && value != "0" {
|
||||
continue // Skip warning for this function
|
||||
}
|
||||
// Also skip warning if _P_REMOVE_UNUSED is enabled (function will be removed)
|
||||
removeValue := pragmaSet.GetPragma("_P_REMOVE_UNUSED")
|
||||
if removeValue != "" && removeValue != "0" {
|
||||
continue // Skip warning for this function (it will be removed)
|
||||
}
|
||||
}
|
||||
|
||||
// Format warning message with file and line info
|
||||
|
|
@ -1034,3 +1040,27 @@ func (fh *FunctionHandler) CheckUnusedFunctions() []string {
|
|||
|
||||
return warnings
|
||||
}
|
||||
|
||||
// GetFunctionsToRemove returns a map of function names that should be removed from assembly output
|
||||
// Functions are removed if they are never called AND have _P_REMOVE_UNUSED pragma enabled
|
||||
func (fh *FunctionHandler) GetFunctionsToRemove() map[string]bool {
|
||||
toRemove := make(map[string]bool)
|
||||
|
||||
for _, funcDecl := range fh.functions {
|
||||
// Skip functions that have been called
|
||||
if fh.calledFunctions[funcDecl.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if pragma indicates we should remove this unused function
|
||||
if fh.pragma != nil {
|
||||
pragmaSet := fh.pragma.GetPragmaSetByIndex(funcDecl.Line.PragmaSetIndex)
|
||||
removeValue := pragmaSet.GetPragma("_P_REMOVE_UNUSED")
|
||||
if removeValue != "" && removeValue != "0" {
|
||||
toRemove[funcDecl.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toRemove
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,16 +206,13 @@ func TestHandleFuncDecl_VoidFunction(t *testing.T) {
|
|||
pragma := preproc.NewPragma()
|
||||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
asm, err := fh.HandleFuncDecl(makeLine("FUNC test_void"))
|
||||
funcName, err := fh.HandleFuncDecl(makeLine("FUNC test_void"))
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
|
||||
if len(asm) != 1 {
|
||||
t.Fatalf("expected 1 asm line, got %d", len(asm))
|
||||
}
|
||||
if asm[0] != "test_void" {
|
||||
t.Errorf("expected label 'test_void', got %q", asm[0])
|
||||
if funcName != "test_void" {
|
||||
t.Fatalf("expected funcName = \"test_void\", got %q", funcName)
|
||||
}
|
||||
|
||||
if !fh.FuncExists("test_void") {
|
||||
|
|
@ -234,13 +231,13 @@ func TestHandleFuncDecl_WithExistingParams(t *testing.T) {
|
|||
st.AddVar("x", "test_func", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
st.AddVar("y", "test_func", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
||||
asm, err := fh.HandleFuncDecl(makeLine("FUNC test_func ( x y )"))
|
||||
funcName, err := fh.HandleFuncDecl(makeLine("FUNC test_func ( x y )"))
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
|
||||
if len(asm) != 1 {
|
||||
t.Fatalf("expected 1 asm line, got %d", len(asm))
|
||||
if funcName != "test_func" {
|
||||
t.Fatalf("expected funcName = \"test_func\", got %q", funcName)
|
||||
}
|
||||
|
||||
funcDecl := fh.findFunc("test_func")
|
||||
|
|
@ -259,13 +256,13 @@ func TestHandleFuncDecl_ImplicitDeclarations(t *testing.T) {
|
|||
pragma := preproc.NewPragma()
|
||||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
asm, err := fh.HandleFuncDecl(makeLine("FUNC test_impl ( {BYTE a} {WORD b} )"))
|
||||
funcName, err := fh.HandleFuncDecl(makeLine("FUNC test_impl ( {BYTE a} {WORD b} )"))
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
|
||||
if len(asm) != 1 {
|
||||
t.Fatalf("expected 1 asm line, got %d", len(asm))
|
||||
if funcName != "test_impl" {
|
||||
t.Fatalf("expected funcName = \"test_impl\", got %q", funcName)
|
||||
}
|
||||
|
||||
// Check that variables were declared
|
||||
|
|
@ -365,7 +362,7 @@ func TestHandleFuncDecl_Errors(t *testing.T) {
|
|||
|
||||
// Special case for redeclaration test
|
||||
if tt.name == "redeclaration" {
|
||||
fh.HandleFuncDecl(makeLine("FUNC duplicate ( {BYTE x} )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC duplicate ( {BYTE x} )"))
|
||||
}
|
||||
|
||||
_, err := fh.HandleFuncDecl(makeLine(tt.line))
|
||||
|
|
@ -389,7 +386,7 @@ func TestHandleFuncCall_VarArgs(t *testing.T) {
|
|||
// Declare function with params
|
||||
st.AddVar("param_a", "test_func", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
st.AddVar("param_b", "test_func", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC test_func ( param_a param_b )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test_func ( param_a param_b )"))
|
||||
|
||||
// Declare caller variables
|
||||
st.AddVar("var_a", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
|
@ -431,7 +428,7 @@ func TestHandleFuncCall_OutParams(t *testing.T) {
|
|||
|
||||
// Declare function with out param
|
||||
st.AddVar("result", "get_result", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC get_result ( out:result )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC get_result ( out:result )"))
|
||||
|
||||
// Declare caller variable
|
||||
st.AddVar("output", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
|
@ -471,7 +468,7 @@ func TestHandleFuncCall_ConstArgs(t *testing.T) {
|
|||
// Declare function
|
||||
st.AddVar("x", "test_const", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
st.AddVar("y", "test_const", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC test_const ( x y )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test_const ( x y )"))
|
||||
|
||||
asm, err := fh.HandleFuncCall(makeLine("CALL test_const ( 42 $1234 )"))
|
||||
if err != nil {
|
||||
|
|
@ -507,7 +504,7 @@ func TestHandleFuncCall_LabelArg(t *testing.T) {
|
|||
|
||||
// Declare function
|
||||
st.AddVar("ptr", "test_label", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC test_label ( ptr )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test_label ( ptr )"))
|
||||
|
||||
asm, err := fh.HandleFuncCall(makeLine("CALL test_label ( @my_label )"))
|
||||
if err != nil {
|
||||
|
|
@ -540,7 +537,7 @@ func TestHandleFuncCall_StringArg(t *testing.T) {
|
|||
|
||||
// Declare function
|
||||
st.AddVar("str_ptr", "print", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC print ( str_ptr )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC print ( str_ptr )"))
|
||||
|
||||
asm, err := fh.HandleFuncCall(makeLine(`CALL print ( "hello" )`))
|
||||
if err != nil {
|
||||
|
|
@ -583,7 +580,7 @@ func TestHandleFuncCall_Errors(t *testing.T) {
|
|||
name: "wrong arg count",
|
||||
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
||||
st.AddVar("x", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
|
||||
},
|
||||
line: "CALL test ( 1 2 )",
|
||||
wantErr: "expected 1 arguments, got 2",
|
||||
|
|
@ -594,7 +591,7 @@ func TestHandleFuncCall_Errors(t *testing.T) {
|
|||
name: "type mismatch",
|
||||
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
||||
st.AddVar("param", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC test ( param )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( param )"))
|
||||
st.AddVar("wvar", "", KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
},
|
||||
line: "CALL test ( wvar )",
|
||||
|
|
@ -604,7 +601,7 @@ func TestHandleFuncCall_Errors(t *testing.T) {
|
|||
name: "const to out param",
|
||||
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
||||
st.AddVar("result", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC test ( out:result )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( out:result )"))
|
||||
},
|
||||
line: "CALL test ( 42 )",
|
||||
wantErr: "out/io parameter",
|
||||
|
|
@ -613,7 +610,7 @@ func TestHandleFuncCall_Errors(t *testing.T) {
|
|||
name: "label to byte param",
|
||||
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
||||
st.AddVar("x", "test", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
|
||||
},
|
||||
line: "CALL test ( @label )",
|
||||
wantErr: "byte parameter",
|
||||
|
|
@ -649,7 +646,7 @@ func TestHandleFuncCall_EmptyParens(t *testing.T) {
|
|||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
// Declare void function
|
||||
fh.HandleFuncDecl(makeLine("FUNC init"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC init"))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -692,7 +689,7 @@ func TestEndFunction(t *testing.T) {
|
|||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
// Declare function (pushes to stack)
|
||||
fh.HandleFuncDecl(makeLine("FUNC test ( {BYTE x} )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test ( {BYTE x} )"))
|
||||
|
||||
if fh.CurrentFunction() != "test" {
|
||||
t.Errorf("current function = %q, want 'test'", fh.CurrentFunction())
|
||||
|
|
@ -717,12 +714,12 @@ func TestCurrentFunction(t *testing.T) {
|
|||
t.Error("expected empty current function initially")
|
||||
}
|
||||
|
||||
fh.HandleFuncDecl(makeLine("FUNC func1 ( {BYTE x} )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC func1 ( {BYTE x} )"))
|
||||
if fh.CurrentFunction() != "func1" {
|
||||
t.Errorf("expected 'func1', got %q", fh.CurrentFunction())
|
||||
}
|
||||
|
||||
fh.HandleFuncDecl(makeLine("FUNC func2 ( {BYTE y} )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC func2 ( {BYTE y} )"))
|
||||
if fh.CurrentFunction() != "func2" {
|
||||
t.Errorf("expected 'func2', got %q", fh.CurrentFunction())
|
||||
}
|
||||
|
|
@ -918,7 +915,7 @@ func TestHandleFuncCall_AbsoluteParams(t *testing.T) {
|
|||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
// Declare function with absolute params
|
||||
fh.HandleFuncDecl(makeLine("FUNC test_abs ( {BYTE param_a @ $fa} {WORD param_b @ $fb} )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC test_abs ( {BYTE param_a @ $fa} {WORD param_b @ $fb} )"))
|
||||
|
||||
// Declare caller variables
|
||||
st.AddVar("var_a", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
|
@ -959,7 +956,7 @@ func TestHandleFuncCall_AbsoluteOutParams(t *testing.T) {
|
|||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
// Declare function with absolute out param
|
||||
fh.HandleFuncDecl(makeLine("FUNC get_result ( out:{BYTE result @ $fa} )"))
|
||||
_, _ = fh.HandleFuncDecl(makeLine("FUNC get_result ( out:{BYTE result @ $fa} )"))
|
||||
|
||||
// Declare caller variable
|
||||
st.AddVar("output", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
|
|
|||
|
|
@ -676,12 +676,20 @@ Special characters in defines:
|
|||
|
||||
Control compiler behavior:
|
||||
|
||||
- `_P_USE_LONG_JUMP`: Use JMP instead of branches for large switch statements
|
||||
- `_P_USE_IMMUTABLE_CODE`: Disable self-modifying code (for ROM)
|
||||
- `_P_USE_CBM_STRINGS`: Use PETSCII encoding for strings
|
||||
- `_P_IGNORE_UNUSED`: Suppress warnings for unused variables
|
||||
- `_P_REMOVE_UNUSED`: Remove unused functions from assembly output (requires explicit pragma on each function)
|
||||
|
||||
```c65
|
||||
#PRAGMA _P_USE_LONG_JUMP 1 // Use JMP instead of branches
|
||||
#PRAGMA _P_USE_IMMUTABLE_CODE 1 // No self-modifying code (for ROM)
|
||||
#PRAGMA _P_USE_CBM_STRINGS 1 // Use PETSCII encoding
|
||||
#PRAGMA _P_IGNORE_UNUSED 1 // Suppress unused variable warnings
|
||||
#PRAGMA _P_IGNORE_UNUSED 0 // Enable unused variable warnings
|
||||
#PRAGMA _P_REMOVE_UNUSED 1 // Remove function if unused
|
||||
#PRAGMA _P_REMOVE_UNUSED 0 // Keep function even if unused (default)
|
||||
```
|
||||
|
||||
### Debug Directives
|
||||
|
|
|
|||
|
|
@ -254,6 +254,12 @@ Sets compiler pragmas (options).
|
|||
- Value: "0" enables warnings, any non-"0" value disables warnings
|
||||
- Note: Do not use `=` sign, use space: `#PRAGMA _P_IGNORE_UNUSED 1`
|
||||
|
||||
**_P_REMOVE_UNUSED**
|
||||
- When enabled (value ≠ "" and ≠ "0"), unused functions with this pragma will be removed from the final assembly output
|
||||
- Requires function to be marked with `#PRAGMA _P_REMOVE_UNUSED 1` at function scope
|
||||
- Functions are only removed if they are never called in the program
|
||||
- The compiler will output an info message to stdout when a function is removed
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
#PRAGMA _P_USE_LONG_JUMP 1
|
||||
|
|
@ -262,6 +268,8 @@ Sets compiler pragmas (options).
|
|||
#PRAGMA _P_USE_CBM_STRINGS 1
|
||||
#PRAGMA _P_IGNORE_UNUSED 1 //suppress unused variable warnings
|
||||
#PRAGMA _P_IGNORE_UNUSED 0 //enable unused variable warnings
|
||||
#PRAGMA _P_REMOVE_UNUSED 1 //remove function if unused
|
||||
#PRAGMA _P_REMOVE_UNUSED 0 //keep function even if unused (default)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
Loading…
Reference in a new issue