diff --git a/.gitignore b/.gitignore index cb9ad25..900c38e 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,8 @@ c65gm *.sym *.prg *.s + +.cache/ +.env +.local/ +opencode-config/package-lock.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..35a5aa2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,167 @@ +# c65gm Agent Instructions + +## Project Overview +c65gm is a high-level 6502 cross-compiler targeting the ACME Cross-Assembler for Commodore 64 and similar 6502-based platforms. It provides a more expressive language for writing 6502 assembly programs with modern programming constructs. + +## Key Documentation Locations + +### Primary Documentation +1. **README.md** - Project overview, building, usage, and requirements +2. **language.md** - Complete language reference with examples (1091 lines) +3. **syntax.md** - Syntax rules and preprocessor directives (646 lines) +4. **commands.md** - Complete command reference (776 lines) + +### Code Structure +- **main.go** - Entry point and command-line interface +- **internal/compiler/** - Core compiler implementation +- **internal/preproc/** - Preprocessor implementation +- **internal/commands/** - Individual command implementations +- **internal/utils/** - Utility functions +- **lib/** - Standard library files for C64 development +- **examples/** - Example programs demonstrating language features + +### Standard Library (lib/) +- **c64start.c65** - Required for C64 executables (BASIC loader setup) +- **c64defs.c65** - C64 hardware definitions (colors, addresses, etc.) +- **c64kernal.c65** - Kernal routine wrappers +- **c64scr.c65** - Screen manipulation functions +- **string.c65** - String handling functions +- **memlib.c65** - Memory management functions +- **fat16/** - FAT16 filesystem support +- **koalalib.c65** - Koala graphics support + +## Language Features to Note + +### Key Concepts +1. **Type system**: BYTE (8-bit) and WORD (16-bit) variables with scope resolution +2. **Functions**: Named functions with parameters and call graph analysis +3. **Control flow**: IF/ENDIF, WHILE/WEND, FOR loops, SWITCH/CASE +4. **Memory operations**: PEEK/POKE/PEEKW/POKEW with zero-page optimization +5. **Preprocessor**: File inclusion, macros, conditional compilation, Starlark scripting +6. **Optimizations**: Constant folding, self-assignment detection +7. **Safety features**: Compile-time detection of overlapping absolute addresses + +### Important Syntax Rules +1. **No operator precedence**: Expressions evaluate strictly left-to-right +2. **Memory-mapped variables**: Use `@` to place variables at specific addresses +3. **Constants**: Prefer `BYTE CONST`/`WORD CONST` over `#DEFINE` +4. **Zero-page optimization**: Place frequently accessed pointers in zero page ($00-$FF) +5. **Include guards**: Always use `#IFNDEF` in library files + +### Special Blocks +1. **ASM blocks**: Inline assembly code +2. **SCRIPT blocks**: Starlark code for compile-time code generation +3. **SCRIPT LIBRARY blocks**: Reusable Starlark functions +4. **SCRIPT MACRO blocks**: Parameterized macros for inline expansion + +## Common Patterns + +### Program Structure +```c65 +#INCLUDE +#INCLUDE + +GOTO start // Jump over function definitions + +// Variables and functions here + +LABEL start // Program entry point +main() +``` + +### Library Structure +```c65 +#IFNDEF __MY_LIBRARY +#DEFINE __MY_LIBRARY = 1 + +GOTO lib_mylib_skip + +// Library code here + +LABEL lib_mylib_skip +#IFEND +``` + +### Memory Operations +- Use `PEEK`/`POKE` for byte access +- Use `PEEKW`/`POKEW` for word (16-bit) access +- For indexed access, address must be a WORD variable in zero page + +## Development Guidelines + +### Environment Constraints +**IMPORTANT**: The agent runs in a Docker container without access to compilers, testing tools, or external build systems. All compilation and testing must be performed by the user. The agent can only: +1. Read and analyze source code +2. Make code changes +3. Provide instructions for the user to compile and test + +### File Access Restrictions +**CRITICAL**: The agent must only access normal project files within the current working directory. The agent must NEVER: +1. Look at files outside the project directory (e.g., `/tmp/`, `/etc/`, `/home/`, etc.) +2. Read or attempt to access system files, environment files (`.env`), or configuration files outside the project +3. Search for or read files containing secrets, credentials, or sensitive information +4. Attempt to access files in parent directories or unrelated paths + +All file operations must be restricted to the project's source code and documentation files only. + +### When Modifying the Compiler +1. Check `internal/commands/` for command implementations +2. Check `internal/compiler/` for core compiler logic +3. Check `internal/preproc/` for preprocessor functionality +4. **DO NOT run tests** - provide instructions for the user to run tests + +### When Adding New Features +1. Follow existing patterns in command implementations +2. Add comprehensive tests +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: +- Run all tests: `go test ./...` +- Run specific package tests: `go test ./internal/compiler` +- Test with verbose output: `go test -v ./...` + +## Common Pitfalls + +1. **Expression evaluation**: Remember no operator precedence +2. **Zero-page requirements**: Indexed memory operations require zero-page addresses +3. **Include guards**: Essential for library files to prevent multiple inclusion +4. **Program structure**: Always use `GOTO start` to jump over function definitions +5. **String handling**: Strings are passed as pointers to WORD parameters + +## Quick Reference + +### Compilation +**IMPORTANT**: The agent cannot compile code. Provide these instructions to the user: +```bash +./c65gm -in input.c65 -out output.asm +acme -f cbm -o output.prg output.asm +``` + +### Environment Variables +- `C65LIBPATH`: Library search path for `#INCLUDE ` + +### Editor Support +- Kate syntax: `editor_syntaxes/kate/` +- Sublime syntax: `editor_syntaxes/sublime/` + +## Where to Look for Specifics + +### Language Syntax +- `language.md`: Sections 1-10 cover all language features +- `syntax.md`: Detailed syntax rules and preprocessor directives +- `commands.md`: Complete command reference with examples + +### Implementation Details +- `internal/compiler/compiler.go`: Main compiler logic +- `internal/commands/*.go`: Individual command implementations +- `internal/preproc/preproc.go`: Preprocessor implementation + +### Examples +- `examples/`: Working example programs +- `lib/`: Standard library usage examples + +### Testing +- `*_test.go` files throughout the codebase +- `examples/`: Functional test programs \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 653f077..0000000 --- a/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM node:18-alpine -WORKDIR /app -RUN apk add --no-cache bash -ENV SHELL=/bin/bash -RUN npm install -g @anthropic-ai/claude-code -CMD ["claude"] diff --git a/claude_code_docker.sh b/claude_code_docker.sh deleted file mode 100644 index 96459ba..0000000 --- a/claude_code_docker.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -export DOCKER_UID=$(id -u) -export DOCKER_GID=$(id -g) -docker compose run --rm claude-c65gm diff --git a/docker-compose.yml b/docker-compose.yml index 8c143f2..957c753 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,15 @@ services: - claude-c65gm: - build: . + opencode-deepseek-c65gm: + #image: ghcr.io/anomalyco/opencode:0.0.0-beta-202604081541 + image: ghcr.io/anomalyco/opencode:latest user: "${DOCKER_UID}:${DOCKER_GID}" + working_dir: /app volumes: - .:/app + - ./opencode-config:/app/.config/opencode + - ./opencode-data:/home/opencode/.local/share/opencode environment: - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - - CLAUDE_CONFIG_DIR=/app/.claude + - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY} - HOME=/app - - DISABLE_AUTOUPDATER=1 stdin_open: true tty: true diff --git a/internal/commands/add.go b/internal/commands/add.go index b3654a5..e824435 100644 --- a/internal/commands/add.go +++ b/internal/commands/add.go @@ -68,13 +68,7 @@ func (c *AddCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) scope := ctx.CurrentScope() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "ADD" { diff --git a/internal/commands/add_test.go b/internal/commands/add_test.go index 7f888ac..27ca183 100644 --- a/internal/commands/add_test.go +++ b/internal/commands/add_test.go @@ -54,9 +54,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "byte + byte -> byte", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD a TO b GIVING c", wantErr: false, @@ -75,9 +75,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "word + word -> word", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 1000) - ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 2000) - ctx.SymbolTable.AddVar("z", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 1000, "test.c65", 1) + ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 2000, "test.c65", 1) + ctx.SymbolTable.AddVar("z", "", compiler.KindWord, 0, "test.c65", 1) }, text: "ADD x TO y GIVING z", wantErr: false, @@ -90,9 +90,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "byte + word -> word", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindWord, 1000) - ctx.SymbolTable.AddVar("c", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindWord, 1000, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindWord, 0, "test.c65", 1) }, text: "ADD a TO b GIVING c", wantErr: false, @@ -111,8 +111,8 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "literal + var -> var", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD 10 TO b GIVING c", wantErr: false, @@ -128,8 +128,8 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "var + literal -> var", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD a TO 20 GIVING c", wantErr: false, @@ -145,7 +145,7 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "literal + literal -> var", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD 15 TO 25 GIVING c", wantErr: false, @@ -161,7 +161,7 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "hex literal", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD $10 TO $20 GIVING c", wantErr: false, @@ -174,9 +174,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "constant usage", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD MAX TO a GIVING c", wantErr: false, @@ -192,9 +192,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "alternative syntax +/->", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD a + b -> c", wantErr: false, @@ -207,7 +207,7 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "unknown variable", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) }, text: "ADD a TO b GIVING c", wantErr: true, @@ -215,9 +215,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "assign to constant", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) }, text: "ADD a TO b GIVING MAX", wantErr: true, @@ -225,7 +225,7 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "wrong parameter count", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) }, text: "ADD a TO b", wantErr: true, @@ -233,9 +233,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "wrong separator #3", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD a AND b GIVING c", wantErr: true, @@ -243,9 +243,9 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "wrong separator #5", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD a TO b INTO c", wantErr: true, @@ -253,7 +253,7 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) { { name: "invalid expression", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, "test.c65", 1) }, text: "ADD @#$% TO 10 GIVING c", wantErr: true, @@ -295,9 +295,9 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "dest = var1 + var2", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, text: "result = a + b", wantErr: false, @@ -316,8 +316,8 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "dest = literal + var", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 5) - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 5, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, text: "result = 100 + x", wantErr: false, @@ -333,8 +333,8 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "dest = var + literal", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 5) - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 5, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, text: "result = x + 50", wantErr: false, @@ -350,7 +350,7 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "dest = literal + literal", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, text: "result = 30 + 70", wantErr: false, @@ -363,9 +363,9 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "word destination", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindWord, 1000) - ctx.SymbolTable.AddVar("b", "", compiler.KindWord, 2000) - ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindWord, 1000, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindWord, 2000, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, text: "result = a + b", wantErr: false, @@ -378,8 +378,8 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "unknown dest variable", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) }, text: "result = a + b", wantErr: true, @@ -387,9 +387,9 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "assign to constant", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) }, text: "MAX = a + b", wantErr: true, @@ -397,9 +397,9 @@ func TestAddCommand_Interpret_NewSyntax(t *testing.T) { { name: "wrong operator (not +)", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, text: "result = a - b", wantErr: true, @@ -439,7 +439,7 @@ func TestAddCommand_Generate(t *testing.T) { { name: "constant folding - both literals to byte", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) return &AddCommand{ param1IsVar: false, param1Value: 10, @@ -457,7 +457,7 @@ func TestAddCommand_Generate(t *testing.T) { { name: "constant folding - both literals to word", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) return &AddCommand{ param1IsVar: false, param1Value: 100, @@ -477,7 +477,7 @@ func TestAddCommand_Generate(t *testing.T) { { name: "constant folding with overflow", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) return &AddCommand{ param1IsVar: false, param1Value: 200, @@ -497,9 +497,9 @@ func TestAddCommand_Generate(t *testing.T) { { name: "byte + byte -> byte", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) return &AddCommand{ param1IsVar: true, param1VarName: "a", @@ -521,9 +521,9 @@ func TestAddCommand_Generate(t *testing.T) { { name: "word + word -> word", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 1000) - ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 2000) - ctx.SymbolTable.AddVar("z", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 1000, "test.c65", 1) + ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 2000, "test.c65", 1) + ctx.SymbolTable.AddVar("z", "", compiler.KindWord, 0, "test.c65", 1) return &AddCommand{ param1IsVar: true, param1VarName: "x", @@ -548,9 +548,9 @@ func TestAddCommand_Generate(t *testing.T) { { name: "byte + word -> word", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("w", "", compiler.KindWord, 1000) - ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("w", "", compiler.KindWord, 1000, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) return &AddCommand{ param1IsVar: true, param1VarName: "a", @@ -575,8 +575,8 @@ func TestAddCommand_Generate(t *testing.T) { { name: "literal + var -> byte", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) return &AddCommand{ param1IsVar: false, param1Value: 5, @@ -597,8 +597,8 @@ func TestAddCommand_Generate(t *testing.T) { { name: "var + literal -> word", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("w", "", compiler.KindWord, 1000) - ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("w", "", compiler.KindWord, 1000, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) return &AddCommand{ param1IsVar: true, param1VarName: "w", @@ -622,9 +622,9 @@ func TestAddCommand_Generate(t *testing.T) { { name: "byte + byte -> word (promotion)", setup: func(ctx *compiler.CompilerContext) *AddCommand { - ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) return &AddCommand{ param1IsVar: true, param1VarName: "a", @@ -678,9 +678,9 @@ func TestAddCommand_Scopes(t *testing.T) { ctx := compiler.NewCompilerContext(preproc.NewPragma()) // Global variables - ctx.SymbolTable.AddVar("globalA", "", compiler.KindByte, 10) - ctx.SymbolTable.AddVar("globalB", "", compiler.KindByte, 20) - ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("globalA", "", compiler.KindByte, 10, "test.c65", 1) + ctx.SymbolTable.AddVar("globalB", "", compiler.KindByte, 20, "test.c65", 1) + ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) // Simulate function declaration to enter scope line := preproc.Line{Text: "FUNC myFunc"} @@ -690,8 +690,8 @@ func TestAddCommand_Scopes(t *testing.T) { } // Function scope variables - ctx.SymbolTable.AddVar("localA", "myFunc", compiler.KindByte, 5) - ctx.SymbolTable.AddVar("localB", "myFunc", compiler.KindByte, 15) + ctx.SymbolTable.AddVar("localA", "myFunc", compiler.KindByte, 5, "test.c65", 1) + ctx.SymbolTable.AddVar("localB", "myFunc", compiler.KindByte, 15, "test.c65", 1) // Test local variables cmd := &AddCommand{} diff --git a/internal/commands/and.go b/internal/commands/and.go index b338013..6c63eab 100644 --- a/internal/commands/and.go +++ b/internal/commands/and.go @@ -68,13 +68,7 @@ func (c *AndCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) scope := ctx.CurrentScope() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "AND" { diff --git a/internal/commands/and_test.go b/internal/commands/and_test.go index 8ae7a68..05c3ce5 100644 --- a/internal/commands/and_test.go +++ b/internal/commands/and_test.go @@ -48,9 +48,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "byte AND byte -> byte (variables)", line: "AND a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -62,9 +62,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "byte AND byte -> word", line: "AND a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -79,9 +79,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "word AND word -> word", line: "AND x WITH y GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x1234) - st.AddVar("y", "", compiler.KindWord, 0x0F0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x0F0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -96,8 +96,8 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "byte AND literal -> byte", line: "AND a WITH $F0 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -109,8 +109,8 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "literal AND byte -> byte", line: "AND 255 WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -122,7 +122,7 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "constant folding: 255 AND 15 -> byte", line: "AND 255 WITH 15 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$0f", @@ -133,7 +133,7 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "constant folding: $FFFF AND $0F0F -> word", line: "AND $FFFF WITH $0F0F GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$0f", @@ -146,9 +146,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "arrow syntax", line: "AND a WITH b -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -160,9 +160,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "word AND byte -> byte", line: "AND wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -174,9 +174,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "word AND byte -> word", line: "AND wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -191,8 +191,8 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "error: unknown destination variable", line: "AND a WITH b GIVING unknown", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -200,9 +200,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "error: wrong separator", line: "AND a TO b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -210,9 +210,9 @@ func TestAndCommand_OldSyntax(t *testing.T) { name: "error: cannot assign to constant", line: "AND a WITH b GIVING MAXVAL", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantErr: true, }, @@ -263,9 +263,9 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "byte & byte -> byte", line: "result = a & b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -277,9 +277,9 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "byte & byte -> word", line: "result = a & b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -294,9 +294,9 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "word & word -> word", line: "result = x & y", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x1234) - st.AddVar("y", "", compiler.KindWord, 0x0F0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x0F0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -311,8 +311,8 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "variable & literal", line: "result = a & $F0", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -324,7 +324,7 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "constant folding", line: "result = 255 & 15", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$0f", @@ -335,7 +335,7 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "constant folding word", line: "result = $FFFF & $1234", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$34", @@ -348,9 +348,9 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "using constant in expression", line: "result = a & MASK", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddConst("MASK", "", compiler.KindByte, 0xF0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddConst("MASK", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -362,8 +362,8 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "error: unknown destination", line: "unknown = a & b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -371,9 +371,9 @@ func TestAndCommand_NewSyntax(t *testing.T) { name: "error: wrong operator", line: "result = a + b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, diff --git a/internal/commands/byte.go b/internal/commands/byte.go index 74b8030..a71d11f 100644 --- a/internal/commands/byte.go +++ b/internal/commands/byte.go @@ -55,13 +55,7 @@ func (c *ByteCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext scope := ctx.FunctionHandler.CurrentFunction() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, ctx.CurrentScope()) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(ctx.CurrentScope()) switch paramCount { case 2: @@ -73,7 +67,7 @@ func (c *ByteCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext return fmt.Errorf("BYTE: invalid identifier %q", varName) } - err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value)) + err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value), line.Filename, line.LineNo) case 4: // BYTE varname = value OR BYTE varname @ address @@ -95,7 +89,7 @@ func (c *ByteCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext if value < 0 || value > 255 { return fmt.Errorf("BYTE: init value %d out of range (0-255)", value) } - err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value)) + err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value), line.Filename, line.LineNo) } else if operator == "@" { // BYTE varname @ address @@ -103,7 +97,7 @@ func (c *ByteCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext return fmt.Errorf("BYTE: absolute address $%X out of range", value) } c.isAbs = true - err = ctx.SymbolTable.AddAbsolute(varName, scope, compiler.KindByte, uint16(value)) + err = ctx.SymbolTable.AddAbsolute(varName, scope, compiler.KindByte, uint16(value), line.Filename, line.LineNo) } else { return fmt.Errorf("BYTE: expected '=' or '@', got %q", operator) @@ -138,7 +132,7 @@ func (c *ByteCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext } c.isConst = true - err = ctx.SymbolTable.AddConst(varName, scope, compiler.KindByte, uint16(value)) + err = ctx.SymbolTable.AddConst(varName, scope, compiler.KindByte, uint16(value), line.Filename, line.LineNo) } if err != nil { diff --git a/internal/commands/byte_test.go b/internal/commands/byte_test.go index 183a6d8..999cda6 100644 --- a/internal/commands/byte_test.go +++ b/internal/commands/byte_test.go @@ -302,7 +302,7 @@ func TestByteCommand_Interpret(t *testing.T) { // For duplicate test, pre-declare the variable if tt.name == "duplicate declaration" { - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) } cmd := &ByteCommand{} @@ -370,7 +370,7 @@ func TestByteCommand_InFunctionScope(t *testing.T) { // Simulate being inside a function // We'd need to push function context, but for now we can test the scoping manually scope := "myFunc" - ctx.SymbolTable.AddVar("localVar", scope, compiler.KindByte, 5) + ctx.SymbolTable.AddVar("localVar", scope, compiler.KindByte, 5, "test.c65", 1) // Check it was added with correct scope sym := ctx.SymbolTable.Lookup("localVar", []string{scope}) @@ -390,8 +390,8 @@ func TestByteCommand_WithConstantExpression(t *testing.T) { ctx := compiler.NewCompilerContext(pragma) // First, declare a constant - ctx.SymbolTable.AddConst("MAXVAL", "", compiler.KindByte, 200) - ctx.SymbolTable.AddConst("OFFSET", "", compiler.KindByte, 10) + ctx.SymbolTable.AddConst("MAXVAL", "", compiler.KindByte, 200, "test.c65", 1) + ctx.SymbolTable.AddConst("OFFSET", "", compiler.KindByte, 10, "test.c65", 1) // Now declare a byte using the constant in an expression cmd := &ByteCommand{} diff --git a/internal/commands/case.go b/internal/commands/case.go index 6a6762d..febb002 100644 --- a/internal/commands/case.go +++ b/internal/commands/case.go @@ -39,13 +39,7 @@ func (c *CaseCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext } scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Parse the case value varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/decr.go b/internal/commands/decr.go index e43537a..6c393f9 100644 --- a/internal/commands/decr.go +++ b/internal/commands/decr.go @@ -97,13 +97,7 @@ func (c *DecrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext } // Old syntax allows absolute addresses - constLookup := func(name string) (int64, bool) { - s := ctx.SymbolTable.Lookup(name, scope) - if s != nil && s.IsConst() { - return int64(s.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) val, evalErr := utils.EvaluateExpression(targetParam, constLookup) if evalErr != nil { diff --git a/internal/commands/for.go b/internal/commands/for.go index 9a40dcc..6955872 100644 --- a/internal/commands/for.go +++ b/internal/commands/for.go @@ -48,13 +48,7 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) } scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Parse variable varName := params[1] diff --git a/internal/commands/for_test.go b/internal/commands/for_test.go index dde0514..cccb3f1 100644 --- a/internal/commands/for_test.go +++ b/internal/commands/for_test.go @@ -20,7 +20,7 @@ func TestForBasicTO(t *testing.T) { name: "byte var TO byte literal", forLine: "FOR i = 0 TO 10", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("i", "", compiler.KindByte, 0) + st.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) }, // Do-while style: initial guard (constant folded - 0<=10 is true, no code) // then loop label @@ -43,7 +43,7 @@ func TestForBasicTO(t *testing.T) { name: "word var TO word literal", forLine: "FOR counter = 0 TO 1000", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("counter", "", compiler.KindWord, 0) + st.AddVar("counter", "", compiler.KindWord, 0, "test.c65", 1) }, // Do-while style: initial guard (constant folded - 0<=1000 is true, no code) wantFor: []string{ @@ -126,7 +126,7 @@ func TestForBasicTO(t *testing.T) { func TestForBreak(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) forCmd := &ForCommand{} breakCmd := &BreakCommand{} @@ -166,8 +166,8 @@ func TestForBreak(t *testing.T) { func TestForNested(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("j", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("j", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -226,8 +226,8 @@ func TestForNested(t *testing.T) { func TestForMixedWithWhile(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -263,8 +263,8 @@ func TestForMixedWithWhile(t *testing.T) { func TestForIllegalNesting(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -320,7 +320,7 @@ func TestNextWithoutFor(t *testing.T) { func TestForWrongParamCount(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) tests := []string{ "FOR i", @@ -348,7 +348,7 @@ func TestForWrongParamCount(t *testing.T) { func TestForInvalidDirection(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) cmd := &ForCommand{} line := preproc.Line{ @@ -369,7 +369,7 @@ func TestForInvalidDirection(t *testing.T) { func TestForDOWNTORejected(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) cmd := &ForCommand{} line := preproc.Line{ @@ -390,7 +390,7 @@ func TestForDOWNTORejected(t *testing.T) { func TestForConstVariable(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddConst("LIMIT", "", compiler.KindByte, 10) + ctx.SymbolTable.AddConst("LIMIT", "", compiler.KindByte, 10, "test.c65", 1) cmd := &ForCommand{} line := preproc.Line{ @@ -431,8 +431,8 @@ func TestForUnknownVariable(t *testing.T) { func TestForConstantEnd(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) forCmd := &ForCommand{} nextCmd := &NextCommand{} @@ -484,7 +484,7 @@ func TestForByteMaxEndValue(t *testing.T) { // This naturally handles the max value case (255) without overflow. pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) forCmd := &ForCommand{} nextCmd := &NextCommand{} @@ -543,7 +543,7 @@ func TestForWordMaxEndValue(t *testing.T) { // Naturally handles the max value case (65535) without overflow. pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("w", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("w", "", compiler.KindWord, 0, "test.c65", 1) forCmd := &ForCommand{} nextCmd := &NextCommand{} diff --git a/internal/commands/gosub.go b/internal/commands/gosub.go index 97109ad..d1252e9 100644 --- a/internal/commands/gosub.go +++ b/internal/commands/gosub.go @@ -70,13 +70,7 @@ func (c *GosubCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Parse target (param 2) targetParam := params[1] diff --git a/internal/commands/goto.go b/internal/commands/goto.go index 78d277f..dccc286 100644 --- a/internal/commands/goto.go +++ b/internal/commands/goto.go @@ -52,13 +52,7 @@ func (c *GotoCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext targetParam := params[1] scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Try ParseOperandParam - handles variables, constants, expressions varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/if.go b/internal/commands/if.go index 496fa79..cfcb810 100644 --- a/internal/commands/if.go +++ b/internal/commands/if.go @@ -40,13 +40,7 @@ func (c *IfCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) } scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Parse param1 varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/if_test.go b/internal/commands/if_test.go index 36c44b7..5185db1 100644 --- a/internal/commands/if_test.go +++ b/internal/commands/if_test.go @@ -20,7 +20,7 @@ func TestIfBasicEqual(t *testing.T) { name: "byte var == byte literal", ifLine: "IF x = 10", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindByte, 0) + st.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) }, wantIf: []string{ "\tlda x", @@ -35,7 +35,7 @@ func TestIfBasicEqual(t *testing.T) { name: "word var == word literal", ifLine: "IF x = 1000", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantIf: []string{ "\tlda x", @@ -116,7 +116,7 @@ func TestIfElseEndif(t *testing.T) { name: "byte var with else", ifLine: "IF x = 10", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindByte, 0) + st.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) }, wantIf: []string{ "\tlda x", @@ -209,7 +209,7 @@ func TestIfAllOperators(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) cmd := &IfCommand{} line := preproc.Line{ @@ -244,8 +244,8 @@ func TestIfAllOperators(t *testing.T) { func TestIfMixedTypes(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 0, "test.c65", 1) cmd := &IfCommand{} line := preproc.Line{ @@ -318,8 +318,8 @@ func TestElseWithoutIf(t *testing.T) { func TestIfNested(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("y", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("y", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -366,8 +366,8 @@ func TestIfNested(t *testing.T) { func TestIfNestedWithElse(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("y", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("y", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -407,7 +407,7 @@ func TestIfLongJump(t *testing.T) { pragma := preproc.NewPragma() pragma.AddPragma("_P_USE_LONG_JUMP", "1") ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) cmd := &IfCommand{} line := preproc.Line{ @@ -440,8 +440,8 @@ func TestIfLongJump(t *testing.T) { func TestIfConstant(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) cmd := &IfCommand{} line := preproc.Line{ @@ -499,7 +499,7 @@ func TestIfWrongParamCount(t *testing.T) { func TestElseWrongParamCount(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) // Setup IF first ifCmd := &IfCommand{} @@ -525,7 +525,7 @@ func TestElseWrongParamCount(t *testing.T) { func TestEndifWrongParamCount(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) // Setup IF first ifCmd := &IfCommand{} diff --git a/internal/commands/incr.go b/internal/commands/incr.go index 60573f2..0752811 100644 --- a/internal/commands/incr.go +++ b/internal/commands/incr.go @@ -97,13 +97,7 @@ func (c *IncrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext } // Old syntax allows absolute addresses - constLookup := func(name string) (int64, bool) { - s := ctx.SymbolTable.Lookup(name, scope) - if s != nil && s.IsConst() { - return int64(s.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) val, evalErr := utils.EvaluateExpression(targetParam, constLookup) if evalErr != nil { diff --git a/internal/commands/incr_decr_test.go b/internal/commands/incr_decr_test.go index 06e1290..f9964db 100644 --- a/internal/commands/incr_decr_test.go +++ b/internal/commands/incr_decr_test.go @@ -77,7 +77,7 @@ func TestIncrCommand_InterpretAndGenerate(t *testing.T) { { name: "INC byte variable old syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, "test.c65", 1) }, line: "INC counter", expectError: false, @@ -94,7 +94,7 @@ func TestIncrCommand_InterpretAndGenerate(t *testing.T) { { name: "INC byte variable new syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, "test.c65", 1) }, line: "counter++", expectError: false, @@ -111,7 +111,7 @@ func TestIncrCommand_InterpretAndGenerate(t *testing.T) { { name: "INC word variable old syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, "test.c65", 1) }, line: "INCREMENT pointer", expectError: false, @@ -135,7 +135,7 @@ func TestIncrCommand_InterpretAndGenerate(t *testing.T) { { name: "INC word variable new syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, "test.c65", 1) }, line: "pointer++", expectError: false, @@ -174,7 +174,7 @@ func TestIncrCommand_InterpretAndGenerate(t *testing.T) { { name: "Error: INC constant variable", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) }, line: "INC MAX", expectError: true, @@ -233,7 +233,7 @@ func TestDecrCommand_InterpretAndGenerate(t *testing.T) { { name: "DEC byte variable old syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, "test.c65", 1) }, line: "DEC counter", expectError: false, @@ -250,7 +250,7 @@ func TestDecrCommand_InterpretAndGenerate(t *testing.T) { { name: "DEC byte variable new syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, "test.c65", 1) }, line: "counter--", expectError: false, @@ -267,7 +267,7 @@ func TestDecrCommand_InterpretAndGenerate(t *testing.T) { { name: "DEC word variable old syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, "test.c65", 1) }, line: "DECREMENT pointer", expectError: false, @@ -294,7 +294,7 @@ func TestDecrCommand_InterpretAndGenerate(t *testing.T) { { name: "DEC word variable new syntax", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, "test.c65", 1) }, line: "pointer--", expectError: false, @@ -332,7 +332,7 @@ func TestDecrCommand_InterpretAndGenerate(t *testing.T) { { name: "Error: DEC constant variable", setup: func(ctx *compiler.CompilerContext) { - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) }, line: "DEC MAX", expectError: true, @@ -385,7 +385,7 @@ func TestIncrDecrCommand_FullNameResolution(t *testing.T) { ctx := compiler.NewCompilerContext(preproc.NewPragma()) // Add a variable with scoped name directly - ctx.SymbolTable.AddVar("counter", "myfunc", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("counter", "myfunc", compiler.KindWord, 0, "test.c65", 1) // Note: CurrentScope will return nil (global) since we're not in a function context // The symbol table lookup will try scoped search and fall back to global diff --git a/internal/commands/let.go b/internal/commands/let.go index 78bff6c..eaee451 100644 --- a/internal/commands/let.go +++ b/internal/commands/let.go @@ -62,13 +62,7 @@ func (c *LetCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) scope := ctx.CurrentScope() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "LET" { diff --git a/internal/commands/let_test.go b/internal/commands/let_test.go index e5db15d..31cd8ed 100644 --- a/internal/commands/let_test.go +++ b/internal/commands/let_test.go @@ -48,8 +48,8 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET byte GET byte", line: "LET a GET b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 10) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) }, wantAsm: []string{ "\tlda b", @@ -60,7 +60,7 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET byte = literal", line: "LET a = 100", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$64", @@ -71,8 +71,8 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET word GET word", line: "LET x GET y", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) - st.AddVar("y", "", compiler.KindWord, 0x1234) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x1234, "test.c65", 1) }, wantAsm: []string{ "\tlda y", @@ -85,7 +85,7 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET word = literal", line: "LET x = $1234", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$34", @@ -98,8 +98,8 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET word GET byte (zero-extend)", line: "LET x GET b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) - st.AddVar("b", "", compiler.KindByte, 100) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 100, "test.c65", 1) }, wantAsm: []string{ "\tlda b", @@ -112,8 +112,8 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET byte GET word (take low byte)", line: "LET b GET x", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("x", "", compiler.KindWord, 0x1234) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -124,7 +124,7 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET word = $0000 (optimization)", line: "LET x = 0", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$00", @@ -136,7 +136,7 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET word = $FFFF (optimization)", line: "LET x = $FFFF", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -148,8 +148,8 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "LET with constant", line: "LET a = MAXVAL", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -160,7 +160,7 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "error: unknown destination", line: "LET unknown GET a", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -168,8 +168,8 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "error: wrong separator", line: "LET a TO b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -177,7 +177,7 @@ func TestLetCommand_OldSyntax(t *testing.T) { name: "error: cannot assign to constant", line: "LET MAXVAL = 100", setupVars: func(st *compiler.SymbolTable) { - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantErr: true, }, @@ -228,8 +228,8 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "byte = byte", line: "a = b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 10) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) }, wantAsm: []string{ "\tlda b", @@ -240,7 +240,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "byte = literal", line: "a = 100", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$64", @@ -251,8 +251,8 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "word = word", line: "x = y", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) - st.AddVar("y", "", compiler.KindWord, 0x1234) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x1234, "test.c65", 1) }, wantAsm: []string{ "\tlda y", @@ -265,7 +265,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "word = literal", line: "x = $1234", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$34", @@ -278,8 +278,8 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "word = byte (zero-extend)", line: "x = b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) - st.AddVar("b", "", compiler.KindByte, 100) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 100, "test.c65", 1) }, wantAsm: []string{ "\tlda b", @@ -292,8 +292,8 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "byte = word (take low byte)", line: "b = x", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("x", "", compiler.KindWord, 0x1234) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -304,7 +304,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "word = 0 (optimization)", line: "x = 0", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$00", @@ -316,7 +316,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "word = $FFFF (optimization)", line: "x = 65535", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -328,7 +328,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "word = $0102 (different bytes, no optimization)", line: "x = $0102", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$02", @@ -341,8 +341,8 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "using constant", line: "a = MAXVAL", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -353,7 +353,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "expression with constant", line: "a = 10+5", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$0f", @@ -364,7 +364,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "error: unknown destination", line: "unknown = a", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -372,7 +372,7 @@ func TestLetCommand_NewSyntax(t *testing.T) { name: "error: cannot assign to constant", line: "MAXVAL = 100", setupVars: func(st *compiler.SymbolTable) { - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantErr: true, }, diff --git a/internal/commands/or.go b/internal/commands/or.go index 540fa1d..c94869b 100644 --- a/internal/commands/or.go +++ b/internal/commands/or.go @@ -68,13 +68,7 @@ func (c *OrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) scope := ctx.CurrentScope() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "OR" { diff --git a/internal/commands/or_test.go b/internal/commands/or_test.go index cc87186..5cdaedf 100644 --- a/internal/commands/or_test.go +++ b/internal/commands/or_test.go @@ -48,9 +48,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "byte OR byte -> byte (variables)", line: "OR a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xF0) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -62,9 +62,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "byte OR byte -> word", line: "OR a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xF0) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -79,9 +79,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "word OR word -> word", line: "OR x WITH y GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x1234) - st.AddVar("y", "", compiler.KindWord, 0x0F0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x0F0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -96,8 +96,8 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "byte OR literal -> byte", line: "OR a WITH $0F GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xF0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -109,8 +109,8 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "literal OR byte -> byte", line: "OR 15 WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0xF0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("b", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$0f", @@ -122,7 +122,7 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "constant folding: 15 OR 240 -> byte", line: "OR 15 WITH 240 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -133,7 +133,7 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "constant folding: $00F0 OR $0F00 -> word", line: "OR $00F0 WITH $0F00 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$f0", @@ -146,9 +146,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "arrow syntax", line: "OR a WITH b -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -160,9 +160,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "word OR byte -> byte", line: "OR wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -174,9 +174,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "word OR byte -> word", line: "OR wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -191,8 +191,8 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "error: unknown destination variable", line: "OR a WITH b GIVING unknown", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -200,9 +200,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "error: wrong separator", line: "OR a TO b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -210,9 +210,9 @@ func TestOrCommand_OldSyntax(t *testing.T) { name: "error: cannot assign to constant", line: "OR a WITH b GIVING MAXVAL", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantErr: true, }, @@ -263,9 +263,9 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "byte | byte -> byte", line: "result = a | b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xF0) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -277,9 +277,9 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "byte | byte -> word", line: "result = a | b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xF0) - st.AddVar("b", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -294,9 +294,9 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "word | word -> word", line: "result = x | y", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x1234) - st.AddVar("y", "", compiler.KindWord, 0x0F0F) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x0F0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -311,8 +311,8 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "variable | literal", line: "result = a | $0F", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xF0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -324,7 +324,7 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "constant folding", line: "result = 15 | 240", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -335,7 +335,7 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "constant folding word", line: "result = $00F0 | $0F00", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$f0", @@ -348,9 +348,9 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "using constant in expression", line: "result = a | BITS", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xF0) - st.AddConst("BITS", "", compiler.KindByte, 0x0F) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xF0, "test.c65", 1) + st.AddConst("BITS", "", compiler.KindByte, 0x0F, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -362,8 +362,8 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "error: unknown destination", line: "unknown = a | b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -371,9 +371,9 @@ func TestOrCommand_NewSyntax(t *testing.T) { name: "error: wrong operator", line: "result = a + b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, diff --git a/internal/commands/origin.go b/internal/commands/origin.go index eccb604..1a069c9 100644 --- a/internal/commands/origin.go +++ b/internal/commands/origin.go @@ -36,13 +36,7 @@ func (c *OriginCommand) Interpret(line preproc.Line, ctx *compiler.CompilerConte } scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) val, err := utils.EvaluateExpression(params[1], constLookup) if err != nil { diff --git a/internal/commands/peek.go b/internal/commands/peek.go index dbf91ac..f3fe7eb 100644 --- a/internal/commands/peek.go +++ b/internal/commands/peek.go @@ -115,13 +115,7 @@ func (c *PeekCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext // Parse address - may have [offset] baseAddr, offsetParam := parseOffset(addrParam) - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Try to parse base address varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/peek_test.go b/internal/commands/peek_test.go index befeee2..1c4c5ec 100644 --- a/internal/commands/peek_test.go +++ b/internal/commands/peek_test.go @@ -19,7 +19,7 @@ func TestPeekOldSyntax(t *testing.T) { name: "peek constant address to byte", line: "PEEK 1024 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda 1024", @@ -30,8 +30,8 @@ func TestPeekOldSyntax(t *testing.T) { name: "peek ZP pointer to byte", line: "PEEK ptr GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddAbsolute("ptr", "", compiler.KindWord, 0x80) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddAbsolute("ptr", "", compiler.KindWord, 0x80, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tldy #0", @@ -43,8 +43,8 @@ func TestPeekOldSyntax(t *testing.T) { name: "peek ZP pointer with offset to byte", line: "PEEK ptr[5] GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddAbsolute("ptr", "", compiler.KindWord, 0x80) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddAbsolute("ptr", "", compiler.KindWord, 0x80, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tldy #5", @@ -56,7 +56,7 @@ func TestPeekOldSyntax(t *testing.T) { name: "peek with -> separator", line: "PEEK 2048 -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda 2048", @@ -107,7 +107,7 @@ func TestPeekNewSyntax(t *testing.T) { name: "new syntax constant address to byte", line: "result = PEEK 1024", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda 1024", @@ -118,8 +118,8 @@ func TestPeekNewSyntax(t *testing.T) { name: "new syntax ZP pointer to byte", line: "result = PEEK ptr", setupVars: func(st *compiler.SymbolTable) { - st.AddAbsolute("ptr", "", compiler.KindWord, 0x80) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddAbsolute("ptr", "", compiler.KindWord, 0x80, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tldy #0", @@ -131,8 +131,8 @@ func TestPeekNewSyntax(t *testing.T) { name: "new syntax ZP pointer with offset", line: "result = PEEK ptr[10]", setupVars: func(st *compiler.SymbolTable) { - st.AddAbsolute("ptr", "", compiler.KindWord, 0x80) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddAbsolute("ptr", "", compiler.KindWord, 0x80, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tldy #10", @@ -144,9 +144,9 @@ func TestPeekNewSyntax(t *testing.T) { name: "new syntax ZP pointer with variable offset", line: "result = PEEK ptr[idx]", setupVars: func(st *compiler.SymbolTable) { - st.AddAbsolute("ptr", "", compiler.KindWord, 0x80) - st.AddVar("idx", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddAbsolute("ptr", "", compiler.KindWord, 0x80, "test.c65", 1) + st.AddVar("idx", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tldy idx", @@ -158,7 +158,7 @@ func TestPeekNewSyntax(t *testing.T) { name: "new syntax to word destination", line: "result = PEEK 2048", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda 2048", @@ -211,7 +211,7 @@ func TestPeekWNewSyntax(t *testing.T) { name: "new syntax constant address", line: "result = PEEKW 1024", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda 1024", @@ -224,8 +224,8 @@ func TestPeekWNewSyntax(t *testing.T) { name: "new syntax ZP pointer", line: "result = PEEKW ptr", setupVars: func(st *compiler.SymbolTable) { - st.AddAbsolute("ptr", "", compiler.KindWord, 0x80) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddAbsolute("ptr", "", compiler.KindWord, 0x80, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tldy #0", @@ -240,8 +240,8 @@ func TestPeekWNewSyntax(t *testing.T) { name: "new syntax ZP pointer with offset", line: "result = PEEKW ptr[8]", setupVars: func(st *compiler.SymbolTable) { - st.AddAbsolute("ptr", "", compiler.KindWord, 0x80) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddAbsolute("ptr", "", compiler.KindWord, 0x80, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tldy #8", @@ -303,7 +303,7 @@ func TestPeekErrors(t *testing.T) { name: "constant destination", line: "PEEK 1024 GIVING CONST", setupVars: func(st *compiler.SymbolTable) { - st.AddConst("CONST", "", compiler.KindByte, 100) + st.AddConst("CONST", "", compiler.KindByte, 100, "test.c65", 1) }, wantError: "cannot PEEK into constant", }, @@ -318,7 +318,7 @@ func TestPeekErrors(t *testing.T) { name: "new syntax constant destination", line: "CONST = PEEK 1024", setupVars: func(st *compiler.SymbolTable) { - st.AddConst("CONST", "", compiler.KindByte, 100) + st.AddConst("CONST", "", compiler.KindByte, 100, "test.c65", 1) }, wantError: "cannot PEEK into constant", }, @@ -326,8 +326,8 @@ func TestPeekErrors(t *testing.T) { name: "offset without ZP pointer old syntax", line: "PEEK addr[5] GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("addr", "", compiler.KindWord, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("addr", "", compiler.KindWord, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantError: "offset", }, @@ -335,8 +335,8 @@ func TestPeekErrors(t *testing.T) { name: "offset without ZP pointer new syntax", line: "result = PEEK addr[5]", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("addr", "", compiler.KindWord, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("addr", "", compiler.KindWord, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantError: "offset", }, @@ -377,7 +377,7 @@ func TestPeekWErrors(t *testing.T) { name: "byte destination", line: "PEEKW 1024 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantError: "must be word type", }, @@ -385,7 +385,7 @@ func TestPeekWErrors(t *testing.T) { name: "new syntax byte destination", line: "result = PEEKW 1024", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantError: "must be word type", }, diff --git a/internal/commands/peekw.go b/internal/commands/peekw.go index 5cd48c1..f9f1ff3 100644 --- a/internal/commands/peekw.go +++ b/internal/commands/peekw.go @@ -116,13 +116,7 @@ func (c *PeekWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex // Parse address - may have [offset] baseAddr, offsetParam := parseOffsetW(addrParam) - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Try to parse base address varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/point.go b/internal/commands/point.go index a7f6f7c..3118ab9 100644 --- a/internal/commands/point.go +++ b/internal/commands/point.go @@ -74,13 +74,7 @@ func (c *PointerCommand) Interpret(line preproc.Line, ctx *compiler.CompilerCont // Parse target (param 4) targetParam := params[3] - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Try ParseOperandParam - handles variables, constants, expressions varName, _, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/poke.go b/internal/commands/poke.go index 431ec41..bef4a59 100644 --- a/internal/commands/poke.go +++ b/internal/commands/poke.go @@ -83,13 +83,7 @@ func (c *PokeCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext addrParam := params[1] baseAddr, offsetParam := parsePOKEOffset(addrParam) - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Try to parse base address varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/pokew.go b/internal/commands/pokew.go index 04d9fc5..ea24bce 100644 --- a/internal/commands/pokew.go +++ b/internal/commands/pokew.go @@ -82,13 +82,7 @@ func (c *PokeWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex addrParam := params[1] baseAddr, offsetParam := parsePOKEWOffset(addrParam) - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Try to parse base address varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/subtr.go b/internal/commands/subtr.go index d2aef93..4ed8914 100644 --- a/internal/commands/subtr.go +++ b/internal/commands/subtr.go @@ -71,13 +71,7 @@ func (c *SubtractCommand) Interpret(line preproc.Line, ctx *compiler.CompilerCon scope := ctx.CurrentScope() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly kw := strings.ToUpper(params[0]) diff --git a/internal/commands/subtr_test.go b/internal/commands/subtr_test.go index 8c3549d..f97c645 100644 --- a/internal/commands/subtr_test.go +++ b/internal/commands/subtr_test.go @@ -50,7 +50,7 @@ func TestSubtractCommand_OldSyntax_FROM(t *testing.T) { name: "SUBTRACT a FROM b (means b-a)", line: "SUBTRACT 5 FROM 10 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$05", @@ -61,9 +61,9 @@ func TestSubtractCommand_OldSyntax_FROM(t *testing.T) { name: "SUBTRACT byte FROM byte -> byte (variables)", line: "SUBTRACT a FROM b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 10) - st.AddVar("b", "", compiler.KindByte, 20) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -76,9 +76,9 @@ func TestSubtractCommand_OldSyntax_FROM(t *testing.T) { name: "SUBT a FROM b -> c with arrow", line: "SUBT a FROM b -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 10) - st.AddVar("b", "", compiler.KindByte, 20) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -91,8 +91,8 @@ func TestSubtractCommand_OldSyntax_FROM(t *testing.T) { name: "SUBTRACT literal FROM variable", line: "SUBTRACT 10 FROM a GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -105,9 +105,9 @@ func TestSubtractCommand_OldSyntax_FROM(t *testing.T) { name: "word FROM word -> word", line: "SUBTRACT x FROM y GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x1000) - st.AddVar("y", "", compiler.KindWord, 0x2000) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x1000, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x2000, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -166,7 +166,7 @@ func TestSubtractCommand_OldSyntax_Minus(t *testing.T) { name: "SUBT a - b (means a-b, no swap)", line: "SUBT 10 - 5 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$05", @@ -177,9 +177,9 @@ func TestSubtractCommand_OldSyntax_Minus(t *testing.T) { name: "SUBT byte - byte -> byte (variables)", line: "SUBT a - b -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("b", "", compiler.KindByte, 10) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -192,9 +192,9 @@ func TestSubtractCommand_OldSyntax_Minus(t *testing.T) { name: "SUBTRACT a - b GIVING c", line: "SUBTRACT a - b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("b", "", compiler.KindByte, 10) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -207,8 +207,8 @@ func TestSubtractCommand_OldSyntax_Minus(t *testing.T) { name: "SUBT variable - literal", line: "SUBT a - 10 -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -221,9 +221,9 @@ func TestSubtractCommand_OldSyntax_Minus(t *testing.T) { name: "word - word -> word", line: "SUBT x - y -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x2000) - st.AddVar("y", "", compiler.KindWord, 0x1000) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x2000, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x1000, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -239,9 +239,9 @@ func TestSubtractCommand_OldSyntax_Minus(t *testing.T) { name: "byte - byte -> word", line: "SUBT a - b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("b", "", compiler.KindByte, 10) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -300,9 +300,9 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "byte - byte -> byte", line: "result = a - b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("b", "", compiler.KindByte, 10) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -315,9 +315,9 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "byte - byte -> word", line: "result = a - b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("b", "", compiler.KindByte, 10) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -333,9 +333,9 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "word - word -> word", line: "result = x - y", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x2000) - st.AddVar("y", "", compiler.KindWord, 0x1000) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x2000, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x1000, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -351,8 +351,8 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "variable - literal", line: "result = a - 10", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 20) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 20, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -365,8 +365,8 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "literal - variable", line: "result = 20 - b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 10) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("b", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -379,7 +379,7 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "constant folding", line: "result = 100 - 25", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$4b", @@ -390,7 +390,7 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "constant folding word", line: "result = $2000 - $1000", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$00", @@ -403,9 +403,9 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "using constant in expression", line: "result = a - OFFSET", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 100) - st.AddConst("OFFSET", "", compiler.KindByte, 10) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 100, "test.c65", 1) + st.AddConst("OFFSET", "", compiler.KindByte, 10, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -418,9 +418,9 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "word - byte -> word", line: "result = wval - bval", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0x10) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0x10, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tsec", @@ -436,8 +436,8 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "error: unknown destination", line: "unknown = a - b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -445,9 +445,9 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "error: wrong operator", line: "result = a + b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -455,9 +455,9 @@ func TestSubtractCommand_NewSyntax(t *testing.T) { name: "error: cannot assign to constant", line: "MAXVAL = a - b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantErr: true, }, diff --git a/internal/commands/switch.go b/internal/commands/switch.go index f822e68..56abba4 100644 --- a/internal/commands/switch.go +++ b/internal/commands/switch.go @@ -33,13 +33,7 @@ func (c *SwitchCommand) Interpret(line preproc.Line, ctx *compiler.CompilerConte } scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Parse the switch variable/expression varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/switch_test.go b/internal/commands/switch_test.go index bd16148..38cb396 100644 --- a/internal/commands/switch_test.go +++ b/internal/commands/switch_test.go @@ -20,7 +20,7 @@ func TestSwitchBasicByte(t *testing.T) { { name: "byte var with byte literal case", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindByte, 0) + st.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) }, caseValue: "10", wantSwitch: []string{}, @@ -96,7 +96,7 @@ func TestSwitchBasicByte(t *testing.T) { func TestSwitchMultipleCases(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -157,7 +157,7 @@ func TestSwitchMultipleCases(t *testing.T) { func TestSwitchWithDefault(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -198,7 +198,7 @@ func TestSwitchWithDefault(t *testing.T) { func TestSwitchCaseAfterDefault(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -234,7 +234,7 @@ func TestSwitchCaseAfterDefault(t *testing.T) { func TestSwitchMultipleDefaults(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -333,7 +333,7 @@ func TestSwitchWrongParamCount(t *testing.T) { func TestCaseWrongParamCount(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -358,7 +358,7 @@ func TestCaseWrongParamCount(t *testing.T) { func TestDefaultWrongParamCount(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -376,7 +376,7 @@ func TestDefaultWrongParamCount(t *testing.T) { func TestEndswitchWrongParamCount(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -394,7 +394,7 @@ func TestEndswitchWrongParamCount(t *testing.T) { func TestSwitchWordType(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("big_val", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("big_val", "", compiler.KindWord, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -434,8 +434,8 @@ func TestSwitchWordType(t *testing.T) { func TestSwitchWithConstant(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddConst("MAX_VAL", "", compiler.KindByte, 100) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddConst("MAX_VAL", "", compiler.KindByte, 100, "test.c65", 1) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -475,8 +475,8 @@ func TestSwitchWithConstant(t *testing.T) { func TestSwitchNested(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("outer", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("inner", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("outer", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("inner", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -546,7 +546,7 @@ func TestSwitchLongJump(t *testing.T) { pragma := preproc.NewPragma() pragma.AddPragma("_P_USE_LONG_JUMP", "1") ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -600,7 +600,7 @@ func TestSwitchLongJump(t *testing.T) { // Verify the pattern is different from normal mode pragmaNormal := preproc.NewPragma() ctxNormal := compiler.NewCompilerContext(pragmaNormal) - ctxNormal.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctxNormal.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdxNormal := pragmaNormal.GetCurrentPragmaSetIndex() @@ -629,7 +629,7 @@ func TestSwitchLongJump(t *testing.T) { func TestSwitchOnConstant(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddConst("VALUE", "", compiler.KindByte, 5) + ctx.SymbolTable.AddConst("VALUE", "", compiler.KindByte, 5, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -688,7 +688,7 @@ func TestSwitchOnConstant(t *testing.T) { func TestSwitchEmptyWithOnlyDefault(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -752,7 +752,7 @@ func TestSwitchComparisonTypes(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", tt.varType, 0) + ctx.SymbolTable.AddVar("x", "", tt.varType, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -801,8 +801,8 @@ func TestSwitchWithVariableCase(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("switch_var", "", tt.switchType, 0) - ctx.SymbolTable.AddVar("case_var", "", tt.caseType, 0) + ctx.SymbolTable.AddVar("switch_var", "", tt.switchType, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("case_var", "", tt.caseType, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -859,7 +859,7 @@ func TestSwitchByteRangeValidation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() diff --git a/internal/commands/while.go b/internal/commands/while.go index 64e14cf..5278867 100644 --- a/internal/commands/while.go +++ b/internal/commands/while.go @@ -41,13 +41,7 @@ func (c *WhileCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex } scope := ctx.CurrentScope() - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Parse param1 varName, varKind, value, isVar, err := compiler.ParseOperandParam( diff --git a/internal/commands/while_test.go b/internal/commands/while_test.go index 04fce56..4992ab2 100644 --- a/internal/commands/while_test.go +++ b/internal/commands/while_test.go @@ -20,7 +20,7 @@ func TestWhileBasicEqual(t *testing.T) { name: "byte var == byte literal", whileLine: "WHILE x = 10", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindByte, 0) + st.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) }, wantWhile: []string{ "_LOOPSTART1", @@ -37,7 +37,7 @@ func TestWhileBasicEqual(t *testing.T) { name: "word var == word literal", whileLine: "WHILE x = 1000", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) }, wantWhile: []string{ "_LOOPSTART1", @@ -125,7 +125,7 @@ func TestWhileAllOperators(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) cmd := &WhileCommand{} line := preproc.Line{ @@ -160,8 +160,8 @@ func TestWhileAllOperators(t *testing.T) { func TestWhileMixedTypes(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 0, "test.c65", 1) cmd := &WhileCommand{} line := preproc.Line{ @@ -194,7 +194,7 @@ func TestWhileMixedTypes(t *testing.T) { func TestWhileBreak(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) whileCmd := &WhileCommand{} breakCmd := &BreakCommand{} @@ -274,8 +274,8 @@ func TestWendWithoutWhile(t *testing.T) { func TestWhileNested(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0) - ctx.SymbolTable.AddVar("j", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("j", "", compiler.KindByte, 0, "test.c65", 1) pragmaIdx := pragma.GetCurrentPragmaSetIndex() @@ -316,7 +316,7 @@ func TestWhileLongJump(t *testing.T) { pragma := preproc.NewPragma() pragma.AddPragma("_P_USE_LONG_JUMP", "1") ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) cmd := &WhileCommand{} line := preproc.Line{ @@ -349,8 +349,8 @@ func TestWhileLongJump(t *testing.T) { func TestWhileConstant(t *testing.T) { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) - ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) - ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0) + ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, "test.c65", 1) + ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, "test.c65", 1) cmd := &WhileCommand{} line := preproc.Line{ diff --git a/internal/commands/word.go b/internal/commands/word.go index c2d9cba..14e8658 100644 --- a/internal/commands/word.go +++ b/internal/commands/word.go @@ -57,13 +57,7 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext scope := ctx.FunctionHandler.CurrentFunction() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, ctx.CurrentScope()) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(ctx.CurrentScope()) switch paramCount { case 2: @@ -75,7 +69,7 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext return fmt.Errorf("WORD: invalid identifier %q", varName) } - err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindWord, uint16(value)) + err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindWord, uint16(value), line.Filename, line.LineNo) case 4: // WORD varname = value OR WORD varname @ address OR WORD varname = "string" @@ -102,7 +96,7 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext // Add string to constant string handler ctx.ConstStrHandler.AddConstStr(label, valueStr, false, pragmaSet) - err = ctx.SymbolTable.AddLabel(varName, scope, label) + err = ctx.SymbolTable.AddLabel(varName, scope, label, line.Filename, line.LineNo) if err != nil { return fmt.Errorf("WORD: %w", err) } @@ -129,7 +123,7 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext labelRef = sym.FullName() } - err = ctx.SymbolTable.AddLabel(varName, scope, labelRef) + err = ctx.SymbolTable.AddLabel(varName, scope, labelRef, line.Filename, line.LineNo) if err != nil { return fmt.Errorf("WORD: %w", err) } @@ -150,7 +144,7 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext if value < 0 || value > 65535 { return fmt.Errorf("WORD: init value %d out of range (0-65535)", value) } - err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindWord, uint16(value)) + err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindWord, uint16(value), line.Filename, line.LineNo) } else if operator == "@" { // WORD varname @ address @@ -158,7 +152,7 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext return fmt.Errorf("WORD: absolute address $%X out of range", value) } c.isAbs = true - err = ctx.SymbolTable.AddAbsolute(varName, scope, compiler.KindWord, uint16(value)) + err = ctx.SymbolTable.AddAbsolute(varName, scope, compiler.KindWord, uint16(value), line.Filename, line.LineNo) } else { return fmt.Errorf("WORD: expected '=' or '@', got %q", operator) @@ -193,7 +187,7 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext } c.isConst = true - err = ctx.SymbolTable.AddConst(varName, scope, compiler.KindWord, uint16(value)) + err = ctx.SymbolTable.AddConst(varName, scope, compiler.KindWord, uint16(value), line.Filename, line.LineNo) } if err != nil { diff --git a/internal/commands/word_test.go b/internal/commands/word_test.go index 81f6dbe..3355651 100644 --- a/internal/commands/word_test.go +++ b/internal/commands/word_test.go @@ -330,7 +330,7 @@ func TestWordCommand_Interpret(t *testing.T) { // For duplicate test, pre-declare the variable if tt.name == "duplicate declaration" { - ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 0, "test.c65", 1) } cmd := &WordCommand{} @@ -396,8 +396,8 @@ func TestWordCommand_WithConstantExpression(t *testing.T) { ctx := compiler.NewCompilerContext(pragma) // First, declare a constant - ctx.SymbolTable.AddConst("MAXVAL", "", compiler.KindWord, 1000) - ctx.SymbolTable.AddConst("OFFSET", "", compiler.KindWord, 500) + ctx.SymbolTable.AddConst("MAXVAL", "", compiler.KindWord, 1000, "test.c65", 1) + ctx.SymbolTable.AddConst("OFFSET", "", compiler.KindWord, 500, "test.c65", 1) // Now declare a word using the constant in an expression cmd := &WordCommand{} @@ -474,7 +474,7 @@ func TestWordCommand_LabelReference(t *testing.T) { // Pre-declare existingVar for the reference test if tt.name == "word referencing another variable" { - ctx.SymbolTable.AddVar("existingVar", "", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("existingVar", "", compiler.KindWord, 0, "test.c65", 1) } cmd := &WordCommand{} @@ -525,7 +525,7 @@ func TestWordCommand_LabelReferenceLocalExpansion(t *testing.T) { } // Declare a local variable in function scope - ctx.SymbolTable.AddVar("localBuffer", "myFunc", compiler.KindWord, 0) + ctx.SymbolTable.AddVar("localBuffer", "myFunc", compiler.KindWord, 0, "test.c65", 1) // Now declare another variable referencing the local cmd := &WordCommand{} diff --git a/internal/commands/xor.go b/internal/commands/xor.go index cf3b422..0431c33 100644 --- a/internal/commands/xor.go +++ b/internal/commands/xor.go @@ -68,13 +68,7 @@ func (c *XorCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) scope := ctx.CurrentScope() // Create constant lookup function - constLookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, scope) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "XOR" { diff --git a/internal/commands/xor_test.go b/internal/commands/xor_test.go index 66b6ec7..82328a1 100644 --- a/internal/commands/xor_test.go +++ b/internal/commands/xor_test.go @@ -48,9 +48,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "byte XOR byte -> byte (variables)", line: "XOR a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0xAA) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0xAA, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -62,9 +62,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "byte XOR byte -> word", line: "XOR a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0xAA) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0xAA, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -78,9 +78,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "word XOR word -> word", line: "XOR x WITH y GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x1234) - st.AddVar("y", "", compiler.KindWord, 0x5678) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x5678, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -95,8 +95,8 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "byte XOR literal -> byte", line: "XOR a WITH $AA GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -108,8 +108,8 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "literal XOR byte -> byte", line: "XOR 255 WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0xAA) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("b", "", compiler.KindByte, 0xAA, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$ff", @@ -121,7 +121,7 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "constant folding: 255 XOR 170 -> byte", line: "XOR 255 WITH 170 GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$55", @@ -132,7 +132,7 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "constant folding: $FFFF XOR $AAAA -> word", line: "XOR $FFFF WITH $AAAA GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$55", @@ -145,9 +145,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "arrow syntax", line: "XOR a WITH b -> result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -159,9 +159,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "word XOR byte -> byte", line: "XOR wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -173,9 +173,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "word XOR byte -> word", line: "XOR wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -189,8 +189,8 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "error: unknown destination variable", line: "XOR a WITH b GIVING unknown", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -198,9 +198,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "error: wrong separator", line: "XOR a TO b GIVING result", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -208,9 +208,9 @@ func TestXorCommand_OldSyntax(t *testing.T) { name: "error: cannot assign to constant", line: "XOR a WITH b GIVING MAXVAL", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddConst("MAXVAL", "", compiler.KindByte, 255) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddConst("MAXVAL", "", compiler.KindByte, 255, "test.c65", 1) }, wantErr: true, }, @@ -261,9 +261,9 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "byte ^ byte -> byte", line: "result = a ^ b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0xAA) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0xAA, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -275,9 +275,9 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "byte ^ byte -> word", line: "result = a ^ b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("b", "", compiler.KindByte, 0xAA) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0xAA, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -291,9 +291,9 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "word ^ word -> word", line: "result = x ^ y", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("x", "", compiler.KindWord, 0x1234) - st.AddVar("y", "", compiler.KindWord, 0x5678) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("x", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("y", "", compiler.KindWord, 0x5678, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda x", @@ -308,8 +308,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "byte ^ byte_const -> word (optimization: skip eor #0)", line: "result = b ^ 5", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("b", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda b", @@ -323,9 +323,9 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "byte ^ word -> word (swapped to word ^ byte)", line: "result = bval ^ wval", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("bval", "", compiler.KindByte, 0xFF) - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("bval", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -339,8 +339,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "byte_const ^ byte -> word (optimization: skip eor #0)", line: "result = 5 ^ b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("b", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$05", @@ -354,8 +354,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "byte ^ word_const -> word (no optimization)", line: "result = b ^ 300", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("b", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda b", @@ -370,8 +370,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "word_const ^ byte -> word (optimization: skip eor #0)", line: "result = 300 ^ b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("b", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("b", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$2c", @@ -385,8 +385,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "self-assignment: word ^= byte (optimization: skip high byte entirely)", line: "wval = wval ^ bval", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0xFF) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0xFF, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -398,8 +398,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "self-assignment reversed: word ^= byte (optimization via swap)", line: "wval = bval ^ wval", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("bval", "", compiler.KindByte, 0xFF) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("bval", "", compiler.KindByte, 0xFF, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -411,7 +411,7 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "self-assignment: word ^= byte_const (optimization: skip high byte entirely)", line: "wval = wval ^ 42", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -423,7 +423,7 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "self-assignment: word ^= word_const (no optimization: high byte needed)", line: "wval = wval ^ 300", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -438,8 +438,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "self-assignment: word ^= word (no optimization: both high bytes needed)", line: "wval = wval ^ wval2", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("wval", "", compiler.KindWord, 0x1234) - st.AddVar("wval2", "", compiler.KindWord, 0x5678) + st.AddVar("wval", "", compiler.KindWord, 0x1234, "test.c65", 1) + st.AddVar("wval2", "", compiler.KindWord, 0x5678, "test.c65", 1) }, wantAsm: []string{ "\tlda wval", @@ -454,8 +454,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "variable ^ literal", line: "result = a ^ $AA", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -467,7 +467,7 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "constant folding", line: "result = 255 ^ 170", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$55", @@ -478,7 +478,7 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "constant folding word", line: "result = $FFFF ^ $AAAA", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("result", "", compiler.KindWord, 0) + st.AddVar("result", "", compiler.KindWord, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda #$55", @@ -491,9 +491,9 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "using constant in expression", line: "result = a ^ MASK", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0xFF) - st.AddConst("MASK", "", compiler.KindByte, 0xAA) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0xFF, "test.c65", 1) + st.AddConst("MASK", "", compiler.KindByte, 0xAA, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantAsm: []string{ "\tlda a", @@ -505,8 +505,8 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "error: unknown destination", line: "unknown = a ^ b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, @@ -514,9 +514,9 @@ func TestXorCommand_NewSyntax(t *testing.T) { name: "error: wrong operator", line: "result = a + b", setupVars: func(st *compiler.SymbolTable) { - st.AddVar("a", "", compiler.KindByte, 0) - st.AddVar("b", "", compiler.KindByte, 0) - st.AddVar("result", "", compiler.KindByte, 0) + st.AddVar("a", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("b", "", compiler.KindByte, 0, "test.c65", 1) + st.AddVar("result", "", compiler.KindByte, 0, "test.c65", 1) }, wantErr: true, }, diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index 6a61b1c..352e940 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -222,6 +222,12 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) { // Analyze for overlapping absolute addresses in function call chains c.checkAbsoluteOverlaps() + // Check for unused variables and print warnings + warnings := c.ctx.SymbolTable.CheckUnused() + for _, warning := range warnings { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", warning) + } + // Assemble final output with headers and footers return c.assembleOutput(codeOutput), nil } diff --git a/internal/compiler/compiler_test.go b/internal/compiler/compiler_test.go index aeca3e0..abb7c03 100644 --- a/internal/compiler/compiler_test.go +++ b/internal/compiler/compiler_test.go @@ -428,7 +428,7 @@ func TestExecuteMacro_LocalVariableExpansion(t *testing.T) { ctx := NewCompilerContext(pragma) // Add a local variable in function scope "testfunc" - ctx.SymbolTable.AddVar("myvar", "testfunc", KindByte, 0) + ctx.SymbolTable.AddVar("myvar", "testfunc", KindByte, 0, "test.c65", 1) // Enter the function scope by declaring the function _, err := ctx.FunctionHandler.HandleFuncDecl(makeLine("FUNC testfunc")) @@ -468,11 +468,11 @@ func TestExecuteMacro_LocalVariableExpansion_MultipleVars(t *testing.T) { 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) + ctx.SymbolTable.AddVar("color_index", "myfunc", KindByte, 0, "test.c65", 1) + ctx.SymbolTable.AddVar("row_color", "myfunc", KindWord, 0, "test.c65", 1) // Add a global table (no function scope) - ctx.SymbolTable.AddVar("scroll_color_table", "", KindWord, 0) + ctx.SymbolTable.AddVar("scroll_color_table", "", KindWord, 0, "test.c65", 1) // Enter the function scope _, err := ctx.FunctionHandler.HandleFuncDecl(makeLine("FUNC myfunc")) @@ -524,7 +524,7 @@ func TestExecuteScript_LocalVariableExpansion(t *testing.T) { ctx := NewCompilerContext(pragma) // Add a local variable in function scope - ctx.SymbolTable.AddVar("counter", "loopfunc", KindByte, 0) + ctx.SymbolTable.AddVar("counter", "loopfunc", KindByte, 0, "test.c65", 1) // Enter the function scope _, err := ctx.FunctionHandler.HandleFuncDecl(makeLine("FUNC loopfunc")) @@ -557,7 +557,7 @@ func TestExecuteScript_Library_GlobalVariableExpansion(t *testing.T) { ctx := NewCompilerContext(pragma) // Add a global variable - ctx.SymbolTable.AddVar("global_counter", "", KindByte, 0) + ctx.SymbolTable.AddVar("global_counter", "", KindByte, 0, "test.c65", 1) // Library script that uses |varname| syntax at global scope // Should expand to the global name (unchanged since it's already global) @@ -597,7 +597,7 @@ func TestExecuteScript_Library_VariableExpansionAtDefinitionTime(t *testing.T) { ctx := NewCompilerContext(pragma) // Add a local variable in a function scope - ctx.SymbolTable.AddVar("local_var", "caller", KindByte, 0) + ctx.SymbolTable.AddVar("local_var", "caller", KindByte, 0, "test.c65", 1) // Library is defined at GLOBAL scope (not inside any function) // So |varname| expansion happens at global scope during library definition @@ -728,7 +728,7 @@ func TestAsmBlock_VariableExpansion_IgnoresComments(t *testing.T) { comp := NewCompiler(pragma) // Add a variable to the symbol table so expansion can work - comp.Context().SymbolTable.AddVar("myvar", "", KindByte, 0) + comp.Context().SymbolTable.AddVar("myvar", "", KindByte, 0, "test.c65", 1) tests := []struct { name string @@ -771,7 +771,7 @@ func TestAsmBlock_VariableExpansion_IgnoresComments(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Create fresh compiler for each test comp := NewCompiler(pragma) - comp.Context().SymbolTable.AddVar("myvar", "", KindByte, 0) + comp.Context().SymbolTable.AddVar("myvar", "", KindByte, 0, "test.c65", 1) lines := []preproc.Line{ { diff --git a/internal/compiler/funchandler.go b/internal/compiler/funchandler.go index 8910098..b274e2e 100644 --- a/internal/compiler/funchandler.go +++ b/internal/compiler/funchandler.go @@ -124,14 +124,14 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) { if isImplicit { // Parse and add implicit variable declaration // Format: {BYTE varname} or {WORD varname} - if err := fh.parseImplicitDecl(implicitDecl, funcName); err != nil { + if err := fh.parseImplicitDecl(implicitDecl, funcName, line.Filename, line.LineNo); 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) } } - // Look up variable in symbol table - sym := fh.symTable.Lookup(varName, []string{funcName}) + // Look up variable in symbol table (validation only, not usage) + 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) @@ -343,12 +343,7 @@ func (fh *FunctionHandler) HandleFuncCall(line preproc.Line) ([]string, error) { } // Use ParseOperandParam for variables, constants, and expressions - constLookup := func(name string) (int64, bool) { - if sym := fh.symTable.Lookup(name, fh.currentFuncs); sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := fh.symTable.ConstantLookupFunc(fh.currentFuncs) _, _, value, isVar, err := ParseOperandParam( arg, fh.symTable, fh.currentFuncs, constLookup) @@ -599,7 +594,7 @@ func (fh *FunctionHandler) processConstValue(value uint16, param *FuncParam, fun } // parseImplicitDecl parses {BYTE varname} or {WORD varname} or {BYTE varname @ address} and adds to symbol table -func (fh *FunctionHandler) parseImplicitDecl(decl string, funcName string) error { +func (fh *FunctionHandler) parseImplicitDecl(decl string, funcName string, filename string, lineNo int) error { parts := strings.Fields(decl) if len(parts) != 2 && len(parts) != 4 { return fmt.Errorf("implicit declaration must be 'TYPE name' or 'TYPE name @ addr', got: %q", decl) @@ -620,7 +615,7 @@ func (fh *FunctionHandler) parseImplicitDecl(decl string, funcName string) error if len(parts) == 2 { // Simple: BYTE name or WORD name - return fh.symTable.AddVar(varName, funcName, kind, 0) + return fh.symTable.AddVar(varName, funcName, kind, 0, filename, lineNo) } // Extended: BYTE name @ address or WORD name @ address @@ -632,13 +627,7 @@ func (fh *FunctionHandler) parseImplicitDecl(decl string, funcName string) error } // Create constant lookup function for address evaluation - constLookup := func(name string) (int64, bool) { - sym := fh.symTable.Lookup(name, []string{funcName}) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + constLookup := fh.symTable.ConstantLookupFunc([]string{funcName}) // Parse address (supports $hex and decimal) using EvaluateExpression addr, err := utils.EvaluateExpression(addrStr, constLookup) @@ -650,7 +639,7 @@ func (fh *FunctionHandler) parseImplicitDecl(decl string, funcName string) error return fmt.Errorf("absolute address $%X out of range", addr) } - return fh.symTable.AddAbsolute(varName, funcName, kind, uint16(addr)) + return fh.symTable.AddAbsolute(varName, funcName, kind, uint16(addr), filename, lineNo) } // EndFunction pops all functions from the stack (called by FEND) diff --git a/internal/compiler/funchandler_test.go b/internal/compiler/funchandler_test.go index ea2f3a2..979f467 100644 --- a/internal/compiler/funchandler_test.go +++ b/internal/compiler/funchandler_test.go @@ -231,8 +231,8 @@ func TestHandleFuncDecl_WithExistingParams(t *testing.T) { fh := NewFunctionHandler(st, ls, csh, pragma) // Pre-declare parameters - st.AddVar("x", "test_func", KindByte, 0) - st.AddVar("y", "test_func", KindWord, 0) + st.AddVar("x", "test_func", KindByte, 0, "test.c65", 1) + st.AddVar("y", "test_func", KindWord, 0, "test.c65", 1) asm, err := fh.HandleFuncDecl(makeLine("FUNC test_func ( x y )")) if err != nil { @@ -340,7 +340,7 @@ func TestHandleFuncDecl_Errors(t *testing.T) { name: "const param", line: "FUNC test ( constval )", preDecl: func(st *SymbolTable) { - st.AddConst("constval", "test", KindByte, 42) + st.AddConst("constval", "test", KindByte, 42, "test.c65", 1) }, wantErr: "cannot be a constant", }, @@ -387,13 +387,13 @@ func TestHandleFuncCall_VarArgs(t *testing.T) { fh := NewFunctionHandler(st, ls, csh, pragma) // Declare function with params - st.AddVar("param_a", "test_func", KindByte, 0) - st.AddVar("param_b", "test_func", KindWord, 0) + st.AddVar("param_a", "test_func", KindByte, 0, "test.c65", 1) + st.AddVar("param_b", "test_func", KindWord, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC test_func ( param_a param_b )")) // Declare caller variables - st.AddVar("var_a", "", KindByte, 0) - st.AddVar("var_b", "", KindWord, 0) + st.AddVar("var_a", "", KindByte, 0, "test.c65", 1) + st.AddVar("var_b", "", KindWord, 0, "test.c65", 1) asm, err := fh.HandleFuncCall(makeLine("CALL test_func ( var_a var_b )")) if err != nil { @@ -430,11 +430,11 @@ func TestHandleFuncCall_OutParams(t *testing.T) { fh := NewFunctionHandler(st, ls, csh, pragma) // Declare function with out param - st.AddVar("result", "get_result", KindByte, 0) + st.AddVar("result", "get_result", KindByte, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC get_result ( out:result )")) // Declare caller variable - st.AddVar("output", "", KindByte, 0) + st.AddVar("output", "", KindByte, 0, "test.c65", 1) asm, err := fh.HandleFuncCall(makeLine("CALL get_result ( output )")) if err != nil { @@ -469,8 +469,8 @@ func TestHandleFuncCall_ConstArgs(t *testing.T) { fh := NewFunctionHandler(st, ls, csh, pragma) // Declare function - st.AddVar("x", "test_const", KindByte, 0) - st.AddVar("y", "test_const", KindWord, 0) + st.AddVar("x", "test_const", KindByte, 0, "test.c65", 1) + st.AddVar("y", "test_const", KindWord, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC test_const ( x y )")) asm, err := fh.HandleFuncCall(makeLine("CALL test_const ( 42 $1234 )")) @@ -506,7 +506,7 @@ func TestHandleFuncCall_LabelArg(t *testing.T) { fh := NewFunctionHandler(st, ls, csh, pragma) // Declare function - st.AddVar("ptr", "test_label", KindWord, 0) + st.AddVar("ptr", "test_label", KindWord, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC test_label ( ptr )")) asm, err := fh.HandleFuncCall(makeLine("CALL test_label ( @my_label )")) @@ -539,7 +539,7 @@ func TestHandleFuncCall_StringArg(t *testing.T) { fh := NewFunctionHandler(st, ls, csh, pragma) // Declare function - st.AddVar("str_ptr", "print", KindWord, 0) + st.AddVar("str_ptr", "print", KindWord, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC print ( str_ptr )")) asm, err := fh.HandleFuncCall(makeLine(`CALL print ( "hello" )`)) @@ -582,7 +582,7 @@ func TestHandleFuncCall_Errors(t *testing.T) { { name: "wrong arg count", setup: func(fh *FunctionHandler, st *SymbolTable) { - st.AddVar("x", "test", KindByte, 0) + st.AddVar("x", "test", KindByte, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC test ( x )")) }, line: "CALL test ( 1 2 )", @@ -593,9 +593,9 @@ func TestHandleFuncCall_Errors(t *testing.T) { { name: "type mismatch", setup: func(fh *FunctionHandler, st *SymbolTable) { - st.AddVar("param", "test", KindByte, 0) + st.AddVar("param", "test", KindByte, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC test ( param )")) - st.AddVar("wvar", "", KindWord, 0) + st.AddVar("wvar", "", KindWord, 0, "test.c65", 1) }, line: "CALL test ( wvar )", wantErr: "type mismatch", @@ -603,7 +603,7 @@ func TestHandleFuncCall_Errors(t *testing.T) { { name: "const to out param", setup: func(fh *FunctionHandler, st *SymbolTable) { - st.AddVar("result", "test", KindByte, 0) + st.AddVar("result", "test", KindByte, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC test ( out:result )")) }, line: "CALL test ( 42 )", @@ -612,7 +612,7 @@ func TestHandleFuncCall_Errors(t *testing.T) { { name: "label to byte param", setup: func(fh *FunctionHandler, st *SymbolTable) { - st.AddVar("x", "test", KindByte, 0) + st.AddVar("x", "test", KindByte, 0, "test.c65", 1) fh.HandleFuncDecl(makeLine("FUNC test ( x )")) }, line: "CALL test ( @label )", @@ -921,8 +921,8 @@ func TestHandleFuncCall_AbsoluteParams(t *testing.T) { fh.HandleFuncDecl(makeLine("FUNC test_abs ( {BYTE param_a @ $fa} {WORD param_b @ $fb} )")) // Declare caller variables - st.AddVar("var_a", "", KindByte, 0) - st.AddVar("var_b", "", KindWord, 0) + st.AddVar("var_a", "", KindByte, 0, "test.c65", 1) + st.AddVar("var_b", "", KindWord, 0, "test.c65", 1) asm, err := fh.HandleFuncCall(makeLine("CALL test_abs ( var_a var_b )")) if err != nil { @@ -962,7 +962,7 @@ func TestHandleFuncCall_AbsoluteOutParams(t *testing.T) { fh.HandleFuncDecl(makeLine("FUNC get_result ( out:{BYTE result @ $fa} )")) // Declare caller variable - st.AddVar("output", "", KindByte, 0) + st.AddVar("output", "", KindByte, 0, "test.c65", 1) asm, err := fh.HandleFuncCall(makeLine("CALL get_result ( output )")) if err != nil { @@ -1059,7 +1059,7 @@ func TestParseImplicitDecl_Absolute(t *testing.T) { st = NewSymbolTable() fh.symTable = st - err := fh.parseImplicitDecl(tt.decl, tt.funcName) + err := fh.parseImplicitDecl(tt.decl, tt.funcName, "test.c65", 1) if tt.wantErr { if err == nil { t.Fatal("expected error, got nil") @@ -1167,7 +1167,7 @@ FEND if strings.ToUpper(parts[0]) == "WORD" { varKind = KindWord } - st.AddVar(parts[1], "", varKind, 0) + st.AddVar(parts[1], "", varKind, 0, "test.c65", 1) } } else if strings.HasPrefix(lineText, "FUNC ") { _, err := fh.HandleFuncDecl(pline) @@ -1276,7 +1276,7 @@ FEND addrStr := strings.TrimPrefix(parts[3], "$") var addr uint16 fmt.Sscanf(addrStr, "%x", &addr) - st.AddAbsolute(parts[1], currentFunc, varKind, addr) + st.AddAbsolute(parts[1], currentFunc, varKind, addr, "test.c65", 1) } } else if strings.HasPrefix(lineText, "FUNC ") { _, err := fh.HandleFuncDecl(pline) @@ -1386,7 +1386,7 @@ FEND if strings.ToUpper(parts[0]) == "WORD" { varKind = KindWord } - st.AddVar(parts[1], "", varKind, 0) + st.AddVar(parts[1], "", varKind, 0, "test.c65", 1) } } else if strings.HasPrefix(lineText, "FUNC ") { _, err := fh.HandleFuncDecl(pline) diff --git a/internal/compiler/scriptexec.go b/internal/compiler/scriptexec.go index ef52758..d0d848c 100644 --- a/internal/compiler/scriptexec.go +++ b/internal/compiler/scriptexec.go @@ -177,13 +177,7 @@ func evaluateMacroArg(arg string, ctx *CompilerContext) (starlark.Value, error) arg = strings.TrimSpace(arg) // Create lookup function for constants - lookup := func(name string) (int64, bool) { - sym := ctx.SymbolTable.Lookup(name, nil) - if sym != nil && sym.IsConst() { - return int64(sym.Value), true - } - return 0, false - } + lookup := ctx.SymbolTable.ConstantLookupFunc(nil) // Try to evaluate as expression (number, constant, arithmetic) val, err := utils.EvaluateExpression(arg, lookup) diff --git a/internal/compiler/symboltable.go b/internal/compiler/symboltable.go index f1bc7d1..ffb99f7 100644 --- a/internal/compiler/symboltable.go +++ b/internal/compiler/symboltable.go @@ -26,14 +26,37 @@ const ( FlagLabelRef ) -// Symbol represents a variable or constant declaration +// Symbol represents a variable, constant, or label reference type Symbol struct { - Name string - Scope string // empty string = global, otherwise function name - Flags SymbolFlags - Value uint16 // init value or const value - AbsAddr uint16 // if FlagAbsolute set - LabelRef string // if FlagLabelRef set + Name string // Variable name + Scope string // Function scope (empty for global) + Flags SymbolFlags // Type and properties + Value uint16 // Initial value for variables, constant value for constants + AbsAddr uint16 // Absolute address (for @ variables) + LabelRef string // Label reference (for label variables) + Usage uint8 // Usage tracking + Filename string // Source file where declared + LineNo int // Line number in source file +} + +// Usage tracking for variables +const ( + UsageNone uint8 = 0 + UsageUsed uint8 = 1 << 0 + // Future flags can be added: + // UsageRead = 1 << 1 + // UsageWritten = 1 << 2 + // UsageAddress = 1 << 3 +) + +// MarkUsed marks the symbol as used (should not be called for constants or absolutes) +func (s *Symbol) MarkUsed() { + s.Usage |= UsageUsed +} + +// IsUsed returns true if the symbol has been marked as used +func (s *Symbol) IsUsed() bool { + return s.Usage&UsageUsed != 0 } // Helper methods for Symbol @@ -89,7 +112,7 @@ func (st *SymbolTable) SetFunctionHandler(fh FunctionHandlerInterface) { } // AddVar adds a regular variable (byte or word) -func (st *SymbolTable) AddVar(name, scope string, kind VarKind, initValue uint16) error { +func (st *SymbolTable) AddVar(name, scope string, kind VarKind, initValue uint16, filename string, lineNo int) error { var flags SymbolFlags switch kind { @@ -105,15 +128,17 @@ func (st *SymbolTable) AddVar(name, scope string, kind VarKind, initValue uint16 } return st.add(&Symbol{ - Name: name, - Scope: scope, - Flags: flags, - Value: initValue, + Name: name, + Scope: scope, + Flags: flags, + Value: initValue, + Filename: filename, + LineNo: lineNo, }) } // AddConst adds a constant (byte or word) -func (st *SymbolTable) AddConst(name, scope string, kind VarKind, value uint16) error { +func (st *SymbolTable) AddConst(name, scope string, kind VarKind, value uint16, filename string, lineNo int) error { var flags SymbolFlags switch kind { @@ -129,15 +154,17 @@ func (st *SymbolTable) AddConst(name, scope string, kind VarKind, value uint16) } return st.add(&Symbol{ - Name: name, - Scope: scope, - Flags: flags, - Value: value, + Name: name, + Scope: scope, + Flags: flags, + Value: value, + Filename: filename, + LineNo: lineNo, }) } // AddAbsolute adds a variable at a fixed memory address -func (st *SymbolTable) AddAbsolute(name, scope string, kind VarKind, addr uint16) error { +func (st *SymbolTable) AddAbsolute(name, scope string, kind VarKind, addr uint16, filename string, lineNo int) error { if addr > 0xFFFF { return fmt.Errorf("absolute address %d exceeds 16-bit range", addr) } @@ -167,20 +194,24 @@ func (st *SymbolTable) AddAbsolute(name, scope string, kind VarKind, addr uint16 } return st.add(&Symbol{ - Name: name, - Scope: scope, - Flags: flags, - AbsAddr: addr, + Name: name, + Scope: scope, + Flags: flags, + AbsAddr: addr, + Filename: filename, + LineNo: lineNo, }) } // AddLabel adds a word variable that references a label -func (st *SymbolTable) AddLabel(name, scope string, labelRef string) error { +func (st *SymbolTable) AddLabel(name, scope string, labelRef string, filename string, lineNo int) error { return st.add(&Symbol{ Name: name, Scope: scope, Flags: FlagWord | FlagLabelRef, LabelRef: labelRef, + Filename: filename, + LineNo: lineNo, }) } @@ -208,12 +239,17 @@ func (st *SymbolTable) add(sym *Symbol) error { // Lookup finds a symbol by name, resolving scope // Searches local scope first (if currentScopes provided), then global +// Marks non-constant, non-absolute variables as used func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol { // Try local scopes first (innermost to outermost) for i := len(currentScopes) - 1; i >= 0; i-- { scope := currentScopes[i] if scopeMap, ok := st.byScope[scope]; ok { if sym, ok := scopeMap[name]; ok { + // Mark as used if it's a regular variable (not constant, not absolute) + if !sym.IsConst() && !sym.IsAbsolute() { + sym.MarkUsed() + } return sym } } @@ -222,6 +258,10 @@ func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol { // Try global scope if scopeMap, ok := st.byScope[""]; ok { if sym, ok := scopeMap[name]; ok { + // Mark as used if it's a regular variable (not constant, not absolute) + if !sym.IsConst() && !sym.IsAbsolute() { + sym.MarkUsed() + } return sym } } @@ -253,6 +293,77 @@ func (st *SymbolTable) ExpandName(name string, currentScopes []string) string { return name } +// LookupWithoutUsage finds a symbol by name without marking it as used +// Used for validation-only lookups (e.g., checking if variable exists) +func (st *SymbolTable) LookupWithoutUsage(name string, currentScopes []string) *Symbol { + // Try local scopes first (innermost to outermost) + for i := len(currentScopes) - 1; i >= 0; i-- { + scope := currentScopes[i] + if scopeMap, ok := st.byScope[scope]; ok { + if sym, ok := scopeMap[name]; ok { + return sym + } + } + } + + // Try global scope + if scopeMap, ok := st.byScope[""]; ok { + if sym, ok := scopeMap[name]; ok { + return sym + } + } + + return nil +} + +// LookupConstant looks up a constant by name and returns its value if found +// Returns (value, true) if symbol exists and is a constant, (0, false) otherwise +func (st *SymbolTable) LookupConstant(name string, currentScopes []string) (int64, bool) { + sym := st.LookupWithoutUsage(name, currentScopes) + if sym != nil && sym.IsConst() { + return int64(sym.Value), true + } + return 0, false +} + +// ConstantLookupFunc returns a ConstantLookup function that captures the current scopes +// This can be used directly with utils.EvaluateExpression +func (st *SymbolTable) ConstantLookupFunc(currentScopes []string) func(string) (int64, bool) { + return func(name string) (int64, bool) { + return st.LookupConstant(name, currentScopes) + } +} + +// CheckUnused returns warnings for unused variables +// Returns slice of warning messages for regular variables (not constants, not absolutes) that were never used +func (st *SymbolTable) CheckUnused() []string { + var warnings []string + for _, sym := range st.symbols { + // Skip constants and absolute variables (they shouldn't track usage) + if sym.IsConst() || sym.IsAbsolute() { + // Sanity check: constants and absolutes should never be marked as used + // If they are, it's a bug in the compiler + if sym.IsUsed() { + // This would be an internal error, but we'll just skip it + continue + } + continue + } + // Check if variable was never used + if !sym.IsUsed() { + // Format warning message with file and line info + var scopeInfo string + if sym.Scope != "" { + scopeInfo = fmt.Sprintf(" in function '%s'", sym.Scope) + } + warning := fmt.Sprintf("%s:%d: warning: variable '%s'%s declared but never used", + sym.Filename, sym.LineNo, sym.Name, scopeInfo) + warnings = append(warnings, warning) + } + } + return warnings +} + // String representation for debugging func (s *Symbol) String() string { var parts []string @@ -277,6 +388,11 @@ func (s *Symbol) String() string { parts = append(parts, fmt.Sprintf("=%d", s.Value)) } + // Add location info if available + if s.Filename != "" { + parts = append(parts, fmt.Sprintf("@%s:%d", s.Filename, s.LineNo)) + } + return strings.Join(parts, " ") } diff --git a/internal/compiler/symboltable_test.go b/internal/compiler/symboltable_test.go index 3a02991..3613f23 100644 --- a/internal/compiler/symboltable_test.go +++ b/internal/compiler/symboltable_test.go @@ -106,7 +106,7 @@ func TestAddVar(t *testing.T) { st := NewSymbolTable() // Add byte var - err := st.AddVar("counter", "", KindByte, 0) + err := st.AddVar("counter", "", KindByte, 0, "test.c65", 1) if err != nil { t.Fatalf("AddVar() error = %v", err) } @@ -123,7 +123,7 @@ func TestAddVar(t *testing.T) { } // Add word var - err = st.AddVar("ptr", "", KindWord, 0x1234) + err = st.AddVar("ptr", "", KindWord, 0x1234, "test.c65", 1) if err != nil { t.Fatalf("AddVar() error = %v", err) } @@ -137,7 +137,7 @@ func TestAddVar(t *testing.T) { } // Test byte value range check - err = st.AddVar("bad", "", KindByte, 256) + err = st.AddVar("bad", "", KindByte, 256, "test.c65", 1) if err == nil { t.Error("expected error for byte value > 255") } @@ -146,7 +146,7 @@ func TestAddVar(t *testing.T) { func TestAddConst(t *testing.T) { st := NewSymbolTable() - err := st.AddConst("MAX", "", KindByte, 255) + err := st.AddConst("MAX", "", KindByte, 255, "test.c65", 1) if err != nil { t.Fatalf("AddConst() error = %v", err) } @@ -166,7 +166,7 @@ func TestAddConst(t *testing.T) { } // Test byte range check - err = st.AddConst("BAD", "", KindByte, 300) + err = st.AddConst("BAD", "", KindByte, 300, "test.c65", 1) if err == nil { t.Error("expected error for byte const > 255") } @@ -176,7 +176,7 @@ func TestAddAbsolute(t *testing.T) { st := NewSymbolTable() // Zero-page byte - err := st.AddAbsolute("ZP_VAR", "", KindByte, 0x80) + err := st.AddAbsolute("ZP_VAR", "", KindByte, 0x80, "test.c65", 1) if err != nil { t.Fatalf("AddAbsolute() error = %v", err) } @@ -193,7 +193,7 @@ func TestAddAbsolute(t *testing.T) { } // Zero-page word pointer - err = st.AddAbsolute("ZP_PTR", "", KindWord, 0xFE) + err = st.AddAbsolute("ZP_PTR", "", KindWord, 0xFE, "test.c65", 1) if err != nil { t.Fatalf("AddAbsolute() error = %v", err) } @@ -204,7 +204,7 @@ func TestAddAbsolute(t *testing.T) { } // Non-zero-page - err = st.AddAbsolute("VIC", "", KindWord, 0xD000) + err = st.AddAbsolute("VIC", "", KindWord, 0xD000, "test.c65", 1) if err != nil { t.Fatalf("AddAbsolute() error = %v", err) } @@ -224,7 +224,7 @@ func TestAddAbsolute(t *testing.T) { func TestAddLabel(t *testing.T) { st := NewSymbolTable() - err := st.AddLabel("handler", "", "irq_vector") + err := st.AddLabel("handler", "", "irq_vector", "test.c65", 1) if err != nil { t.Fatalf("AddLabel() error = %v", err) } @@ -247,19 +247,19 @@ func TestAddLabel(t *testing.T) { func TestRedeclaration(t *testing.T) { st := NewSymbolTable() - err := st.AddVar("test", "", KindByte, 0) + err := st.AddVar("test", "", KindByte, 0, "test.c65", 1) if err != nil { t.Fatalf("first AddVar() error = %v", err) } // Attempt redeclaration - err = st.AddVar("test", "", KindByte, 0) + err = st.AddVar("test", "", KindByte, 0, "test.c65", 1) if err == nil { t.Error("expected error on redeclaration") } // Different scope should be OK - err = st.AddVar("test", "main", KindByte, 0) + err = st.AddVar("test", "main", KindByte, 0, "test.c65", 1) if err != nil { t.Errorf("AddVar() with different scope error = %v", err) } @@ -269,13 +269,13 @@ func TestLookup(t *testing.T) { st := NewSymbolTable() // Global variable - st.AddVar("global", "", KindByte, 0) + st.AddVar("global", "", KindByte, 0, "test.c65", 1) // Local in main - st.AddVar("local", "main", KindByte, 0) + st.AddVar("local", "main", KindByte, 0, "test.c65", 1) // Local in nested function - st.AddVar("inner", "main_helper", KindByte, 0) + st.AddVar("inner", "main_helper", KindByte, 0, "test.c65", 1) tests := []struct { name string @@ -350,8 +350,8 @@ func TestLookup(t *testing.T) { func TestExpandName(t *testing.T) { st := NewSymbolTable() - st.AddVar("global", "", KindByte, 0) - st.AddVar("local", "main", KindByte, 0) + st.AddVar("global", "", KindByte, 0, "test.c65", 1) + st.AddVar("local", "main", KindByte, 0, "test.c65", 1) tests := []struct { name string @@ -379,7 +379,7 @@ func TestInsertionOrder(t *testing.T) { names := []string{"first", "second", "third"} for _, name := range names { - st.AddVar(name, "", KindByte, 0) + st.AddVar(name, "", KindByte, 0, "test.c65", 1) } symbols := st.Symbols() @@ -401,8 +401,8 @@ func TestCount(t *testing.T) { t.Errorf("initial Count() = %d, want 0", st.Count()) } - st.AddVar("a", "", KindByte, 0) - st.AddVar("b", "", KindByte, 0) + st.AddVar("a", "", KindByte, 0, "test.c65", 1) + st.AddVar("b", "", KindByte, 0, "test.c65", 1) if st.Count() != 2 { t.Errorf("Count() = %d, want 2", st.Count()) @@ -418,7 +418,7 @@ func TestSymbolString(t *testing.T) { { name: "byte variable", setup: func(st *SymbolTable) *Symbol { - st.AddVar("test", "", KindByte, 0) + st.AddVar("test", "", KindByte, 0, "test.c65", 1) return st.Get("test") }, contains: []string{"Name=test", "BYTE"}, @@ -426,7 +426,7 @@ func TestSymbolString(t *testing.T) { { name: "word constant", setup: func(st *SymbolTable) *Symbol { - st.AddConst("MAX", "", KindWord, 65535) + st.AddConst("MAX", "", KindWord, 65535, "test.c65", 1) return st.Get("MAX") }, contains: []string{"Name=MAX", "WORD", "CONST=65535"}, @@ -434,7 +434,7 @@ func TestSymbolString(t *testing.T) { { name: "zero-page pointer", setup: func(st *SymbolTable) *Symbol { - st.AddAbsolute("ptr", "", KindWord, 0x80) + st.AddAbsolute("ptr", "", KindWord, 0x80, "test.c65", 1) return st.Get("ptr") }, contains: []string{"Name=ptr", "WORD", "@$0080", "ZP"}, @@ -442,7 +442,7 @@ func TestSymbolString(t *testing.T) { { name: "label reference", setup: func(st *SymbolTable) *Symbol { - st.AddLabel("handler", "", "irq") + st.AddLabel("handler", "", "irq", "test.c65", 1) return st.Get("handler") }, contains: []string{"Name=handler", "->irq"}, @@ -484,9 +484,9 @@ func TestGenerateConstants(t *testing.T) { st := NewSymbolTable() // Add some constants - st.AddConst("MAX", "", KindByte, 255) - st.AddConst("SIZE", "", KindWord, 0x1234) - st.AddVar("notconst", "", KindByte, 0) // should be skipped + st.AddConst("MAX", "", KindByte, 255, "test.c65", 1) + st.AddConst("SIZE", "", KindWord, 0x1234, "test.c65", 1) + st.AddVar("notconst", "", KindByte, 0, "test.c65", 1) // should be skipped lines := GenerateConstants(st) @@ -520,14 +520,14 @@ func TestGenerateAbsolutes(t *testing.T) { st := NewSymbolTable() // Zero-page - st.AddAbsolute("ZP_VAR", "", KindByte, 0x80) - st.AddAbsolute("ZP_PTR", "", KindWord, 0xFE) + st.AddAbsolute("ZP_VAR", "", KindByte, 0x80, "test.c65", 1) + st.AddAbsolute("ZP_PTR", "", KindWord, 0xFE, "test.c65", 1) // Non-zero-page - st.AddAbsolute("VIC", "", KindWord, 0xD000) + st.AddAbsolute("VIC", "", KindWord, 0xD000, "test.c65", 1) // Regular var (should be skipped) - st.AddVar("regular", "", KindByte, 0) + st.AddVar("regular", "", KindByte, 0, "test.c65", 1) lines := GenerateAbsolutes(st) @@ -564,19 +564,19 @@ func TestGenerateVariables(t *testing.T) { st := NewSymbolTable() // Byte variable - st.AddVar("counter", "", KindByte, 42) + st.AddVar("counter", "", KindByte, 42, "test.c65", 1) // Word variable - st.AddVar("ptr", "", KindWord, 0x1234) + st.AddVar("ptr", "", KindWord, 0x1234, "test.c65", 1) // Label reference - st.AddLabel("handler", "", "irq_routine") + st.AddLabel("handler", "", "irq_routine", "test.c65", 1) // Const (should be skipped) - st.AddConst("SKIP", "", KindByte, 99) + st.AddConst("SKIP", "", KindByte, 99, "test.c65", 1) // Absolute (should be skipped) - st.AddAbsolute("SKIP2", "", KindByte, 0x80) + st.AddAbsolute("SKIP2", "", KindByte, 0x80, "test.c65", 1) lines := GenerateVariables(st) @@ -633,7 +633,7 @@ func TestGenerateEmpty(t *testing.T) { } // Only variables (no constants/absolutes) - st.AddVar("test", "", KindByte, 0) + st.AddVar("test", "", KindByte, 0, "test.c65", 1) if lines := GenerateConstants(st); lines != nil { t.Error("expected nil when no constants exist") @@ -646,9 +646,9 @@ func TestGenerateEmpty(t *testing.T) { func TestGenerateScopedVariables(t *testing.T) { st := NewSymbolTable() - st.AddVar("global", "", KindByte, 0) - st.AddVar("local", "main", KindByte, 0) - st.AddVar("nested", "main_helper", KindByte, 0) + st.AddVar("global", "", KindByte, 0, "test.c65", 1) + st.AddVar("local", "main", KindByte, 0, "test.c65", 1) + st.AddVar("nested", "main_helper", KindByte, 0, "test.c65", 1) lines := GenerateVariables(st) output := strings.Join(lines, "\n") @@ -668,9 +668,9 @@ func TestGenerateScopedVariables(t *testing.T) { func TestGenerateHexLowercase(t *testing.T) { st := NewSymbolTable() - st.AddConst("TEST", "", KindByte, 0xAB) - st.AddAbsolute("ADDR", "", KindWord, 0xDEAD) - st.AddVar("VAR", "", KindWord, 0xBEEF) + st.AddConst("TEST", "", KindByte, 0xAB, "test.c65", 1) + st.AddAbsolute("ADDR", "", KindWord, 0xDEAD, "test.c65", 1) + st.AddVar("VAR", "", KindWord, 0xBEEF, "test.c65", 1) constLines := GenerateConstants(st) absLines := GenerateAbsolutes(st) @@ -693,3 +693,381 @@ func TestGenerateHexLowercase(t *testing.T) { t.Error("expected lowercase hex in variable") } } + +func TestUsageTracking(t *testing.T) { + t.Run("Unused variable generates warning", func(t *testing.T) { + st := NewSymbolTable() + + // Add an unused variable + err := st.AddVar("unused", "", KindByte, 0, "test.c65", 10) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + + // Check that it's marked as unused + sym := st.Get("unused") + if sym == nil { + t.Fatal("symbol not found") + } + if sym.IsUsed() { + t.Errorf("IsUsed() = true, want false") + } + + // Check that warning is generated + warnings := st.CheckUnused() + if len(warnings) != 1 { + t.Fatalf("CheckUnused() returned %d warnings, want 1", len(warnings)) + } + + expected := "test.c65:10: warning: variable 'unused' declared but never used" + if warnings[0] != expected { + t.Errorf("warning = %q, want %q", warnings[0], expected) + } + }) + + t.Run("Used variable doesn't generate warning", func(t *testing.T) { + st := NewSymbolTable() + + // Add a variable + err := st.AddVar("used", "", KindByte, 0, "test.c65", 20) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + + // Mark it as used via Lookup (simulating actual usage) + sym := st.Lookup("used", []string{}) + if sym == nil { + t.Fatal("symbol not found") + } + + // Check that it's marked as used + if !sym.IsUsed() { + t.Errorf("IsUsed() = false, want true") + } + + // Check that no warning is generated + warnings := st.CheckUnused() + if len(warnings) != 0 { + t.Errorf("CheckUnused() returned %d warnings, want 0: %v", len(warnings), warnings) + } + }) + + t.Run("Constants are ignored", func(t *testing.T) { + st := NewSymbolTable() + + // Add a constant (should not generate warning) + err := st.AddConst("MAX", "", KindByte, 100, "test.c65", 30) + if err != nil { + t.Fatalf("AddConst() error = %v", err) + } + + // Check that no warning is generated + warnings := st.CheckUnused() + if len(warnings) != 0 { + t.Errorf("CheckUnused() returned %d warnings for constant, want 0: %v", len(warnings), warnings) + } + + // Verify constant is not marked as used (constants shouldn't track usage) + sym := st.Get("MAX") + if sym.IsUsed() { + t.Errorf("Constant IsUsed() = true, want false") + } + }) + + t.Run("Absolute variables are ignored", func(t *testing.T) { + st := NewSymbolTable() + + // Add an absolute variable (should not generate warning) + err := st.AddAbsolute("SCREEN", "", KindWord, 0xD000, "test.c65", 40) + if err != nil { + t.Fatalf("AddAbsolute() error = %v", err) + } + + // Check that no warning is generated + warnings := st.CheckUnused() + if len(warnings) != 0 { + t.Errorf("CheckUnused() returned %d warnings for absolute variable, want 0: %v", len(warnings), warnings) + } + + // Verify absolute variable is not marked as used (absolutes shouldn't track usage) + sym := st.Get("SCREEN") + if sym.IsUsed() { + t.Errorf("Absolute variable IsUsed() = true, want false") + } + }) + + t.Run("Function-scoped variables", func(t *testing.T) { + st := NewSymbolTable() + + // Add a global unused variable + err := st.AddVar("global_unused", "", KindByte, 0, "test.c65", 50) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + + // Add a function-scoped unused variable + err = st.AddVar("local_unused", "myFunc", KindByte, 0, "test.c65", 60) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + + // Add a function-scoped used variable + err = st.AddVar("local_used", "myFunc", KindByte, 0, "test.c65", 70) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + st.Lookup("local_used", []string{"myFunc"}) + + // Check warnings + warnings := st.CheckUnused() + if len(warnings) != 2 { + t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings) + } + + // Check warning formats + expectedGlobal := "test.c65:50: warning: variable 'global_unused' declared but never used" + expectedLocal := "test.c65:60: warning: variable 'local_unused' in function 'myFunc' declared but never used" + + foundGlobal := false + foundLocal := false + for _, w := range warnings { + if w == expectedGlobal { + foundGlobal = true + } + if w == expectedLocal { + foundLocal = true + } + } + + if !foundGlobal { + t.Errorf("Missing warning for global variable: %q", expectedGlobal) + } + if !foundLocal { + t.Errorf("Missing warning for local variable: %q", expectedLocal) + } + }) + + t.Run("Multiple lookups don't affect usage flag", func(t *testing.T) { + st := NewSymbolTable() + + // Add a variable + err := st.AddVar("counter", "", KindByte, 0, "test.c65", 80) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + + // Lookup multiple times (should only mark as used once) + sym1 := st.Lookup("counter", []string{}) + sym2 := st.Lookup("counter", []string{}) + sym3 := st.Lookup("counter", []string{}) + + if sym1 != sym2 || sym2 != sym3 { + t.Error("Lookup should return same symbol instance") + } + + // Should still be marked as used (idempotent) + if !sym1.IsUsed() { + t.Errorf("IsUsed() after multiple lookups = false, want true") + } + + // No warnings should be generated + warnings := st.CheckUnused() + if len(warnings) != 0 { + t.Errorf("CheckUnused() returned %d warnings for used variable, want 0", len(warnings)) + } + }) + + t.Run("LookupWithoutUsage doesn't mark as used", func(t *testing.T) { + st := NewSymbolTable() + + // Add a variable + err := st.AddVar("temp", "", KindByte, 0, "test.c65", 90) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + + // Use LookupWithoutUsage (validation only, not actual usage) + sym := st.LookupWithoutUsage("temp", []string{}) + if sym == nil { + t.Fatal("symbol not found") + } + + // Should NOT be marked as used + if sym.IsUsed() { + t.Errorf("IsUsed() after LookupWithoutUsage = true, want false") + } + + // Warning should be generated + warnings := st.CheckUnused() + if len(warnings) != 1 { + t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings)) + } + }) + + t.Run("Mixed variables with usage", func(t *testing.T) { + st := NewSymbolTable() + + // Add various types of variables + st.AddVar("unused1", "", KindByte, 0, "test.c65", 100) + st.AddVar("used1", "", KindByte, 0, "test.c65", 101) + st.AddConst("CONST1", "", KindByte, 255, "test.c65", 102) + st.AddAbsolute("ABS1", "", KindWord, 0xC000, "test.c65", 103) + st.AddVar("unused2", "func1", KindWord, 0, "test.c65", 104) + st.AddVar("used2", "func1", KindWord, 0, "test.c65", 105) + + // Mark some as used + st.Lookup("used1", []string{}) + st.Lookup("used2", []string{"func1"}) + + // Check warnings + warnings := st.CheckUnused() + if len(warnings) != 2 { + t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings) + } + + // Verify correct warnings + expected1 := "test.c65:100: warning: variable 'unused1' declared but never used" + expected2 := "test.c65:104: warning: variable 'unused2' in function 'func1' declared but never used" + + found1 := false + found2 := false + for _, w := range warnings { + if w == expected1 { + found1 = true + } + if w == expected2 { + found2 = true + } + } + + if !found1 { + t.Errorf("Missing warning: %q", expected1) + } + if !found2 { + t.Errorf("Missing warning: %q", expected2) + } + }) + + t.Run("Label references", func(t *testing.T) { + st := NewSymbolTable() + + // Add a label reference variable + err := st.AddLabel("handler", "", "irq_vector", "test.c65", 110) + if err != nil { + t.Fatalf("AddLabel() error = %v", err) + } + + // Label references are word variables that reference labels + // They should be treated like regular variables for usage tracking + sym := st.Get("handler") + if !sym.Has(FlagLabelRef) { + t.Error("Expected FlagLabelRef") + } + if !sym.IsWord() { + t.Error("Label reference should be word") + } + + // Since we haven't used it, it should generate a warning + warnings := st.CheckUnused() + if len(warnings) != 1 { + t.Errorf("CheckUnused() returned %d warnings for label reference, want 1", len(warnings)) + } + + // Mark it as used + st.Lookup("handler", []string{}) + + // Now no warning should be generated + warnings = st.CheckUnused() + if len(warnings) != 0 { + t.Errorf("CheckUnused() returned %d warnings for used label reference, want 0", len(warnings)) + } + }) + + t.Run("LookupConstant doesn't mark as used", func(t *testing.T) { + st := NewSymbolTable() + + // Add a regular variable + err := st.AddVar("var1", "", KindByte, 0, "test.c65", 120) + if err != nil { + t.Fatalf("AddVar() error = %v", err) + } + + // Add a constant + err = st.AddConst("CONST1", "", KindByte, 100, "test.c65", 121) + if err != nil { + t.Fatalf("AddConst() error = %v", err) + } + + // Lookup constant (should not mark variable as used) + val, found := st.LookupConstant("CONST1", []string{}) + if !found { + t.Fatal("constant not found") + } + if val != 100 { + t.Errorf("constant value = %d, want 100", val) + } + + // LookupConstant on non-constant variable (should not mark as used) + val, found = st.LookupConstant("var1", []string{}) + if found { + t.Error("LookupConstant should return false for non-constant") + } + + // Check that variable is still unused + sym := st.Get("var1") + if sym.IsUsed() { + t.Errorf("IsUsed() after LookupConstant = true, want false") + } + + // Warning should be generated for the variable + warnings := st.CheckUnused() + if len(warnings) != 1 { + t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings)) + } + + expected := "test.c65:120: warning: variable 'var1' declared but never used" + if warnings[0] != expected { + t.Errorf("warning = %q, want %q", warnings[0], expected) + } + }) + + t.Run("Lookup doesn't mark constants or absolutes as used", func(t *testing.T) { + st := NewSymbolTable() + + // Add a constant + err := st.AddConst("MAX", "", KindByte, 255, "test.c65", 130) + if err != nil { + t.Fatalf("AddConst() error = %v", err) + } + + // Add an absolute variable + err = st.AddAbsolute("VIC", "", KindWord, 0xD000, "test.c65", 131) + if err != nil { + t.Fatalf("AddAbsolute() error = %v", err) + } + + // Lookup constant (should not mark as used) + symConst := st.Lookup("MAX", []string{}) + if symConst == nil { + t.Fatal("constant not found") + } + if symConst.IsUsed() { + t.Error("constant should not be marked as used after Lookup") + } + + // Lookup absolute variable (should not mark as used) + symAbs := st.Lookup("VIC", []string{}) + if symAbs == nil { + t.Fatal("absolute variable not found") + } + if symAbs.IsUsed() { + t.Error("absolute variable should not be marked as used after Lookup") + } + + // No warnings should be generated + warnings := st.CheckUnused() + if len(warnings) != 0 { + t.Errorf("CheckUnused() returned %d warnings for constants/absolutes, want 0", len(warnings)) + } + }) +} diff --git a/opencode-config/opencode.json b/opencode-config/opencode.json new file mode 100644 index 0000000..86da758 --- /dev/null +++ b/opencode-config/opencode.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://opencode.ai/config.json", + "agent": { + "plan": { + "model": "deepseek/deepseek-reasoner", + "description": "Planning and architecture analysis using DeepSeek Reasoner" + }, + "build": { + "model": "deepseek/deepseek-chat", + "description": "Implementation and coding using DeepSeek Chat" + } + } +} \ No newline at end of file diff --git a/opencode-docker.sh b/opencode-docker.sh new file mode 100644 index 0000000..986e629 --- /dev/null +++ b/opencode-docker.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail + +export DOCKER_UID=$(id -u) +export DOCKER_GID=$(id -g) + +# Load from .env if it exists +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) +fi + +docker compose run --rm opencode-deepseek-c65gm