104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
package compiler
|
|
|
|
import (
|
|
"c65gm/internal/utils"
|
|
"fmt"
|
|
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
// Command represents a script command that can interpret source lines and generate assembly
|
|
type Command interface {
|
|
// WillHandle checks if this command can handle the given line
|
|
// Should clear any internal state and return true if it handles this line
|
|
WillHandle(line preproc.Line) bool
|
|
|
|
// Interpret parses and validates the line, storing state in the command
|
|
// Returns error if line is malformed or invalid
|
|
Interpret(line preproc.Line, ctx *CompilerContext) error
|
|
|
|
// Generate produces assembly output based on previously interpreted line
|
|
// Returns assembly lines and error if generation fails
|
|
Generate(ctx *CompilerContext) ([]string, error)
|
|
}
|
|
|
|
// CommandRegistry manages registered commands and dispatches lines to appropriate handlers
|
|
type CommandRegistry struct {
|
|
commands []Command
|
|
}
|
|
|
|
// NewCommandRegistry creates a new command registry
|
|
func NewCommandRegistry() *CommandRegistry {
|
|
return &CommandRegistry{
|
|
commands: make([]Command, 0),
|
|
}
|
|
}
|
|
|
|
// Register adds a command to the registry
|
|
func (r *CommandRegistry) Register(cmd Command) {
|
|
r.commands = append(r.commands, cmd)
|
|
}
|
|
|
|
// FindHandler finds the first command that will handle the given line
|
|
// Returns the command and true if found, nil and false otherwise
|
|
func (r *CommandRegistry) FindHandler(line preproc.Line) (Command, bool) {
|
|
for _, cmd := range r.commands {
|
|
if cmd.WillHandle(line) {
|
|
return cmd, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// UnhandledLineError represents an error when no command handles a source line
|
|
type UnhandledLineError struct {
|
|
Line preproc.Line
|
|
}
|
|
|
|
func (e *UnhandledLineError) Error() string {
|
|
return fmt.Sprintf("%s:%d: unhandled line: %s", e.Line.Filename, e.Line.LineNo, e.Line.Text)
|
|
}
|
|
|
|
// ParseOperandParam parses a command parameter that can be a variable or expression
|
|
// Returns: varName, varKind, value, isVar, error
|
|
func ParseOperandParam(
|
|
param string,
|
|
symTable *SymbolTable,
|
|
scope []string,
|
|
constLookup utils.ConstantLookup,
|
|
) (varName string, varKind VarKind, value uint16, isVar bool, err error) {
|
|
// Try variable lookup first
|
|
sym := symTable.Lookup(param, scope)
|
|
if sym != nil {
|
|
varKind = sym.GetVarKind()
|
|
value = sym.Value
|
|
|
|
// Constants are treated as literals (inlined), not variables
|
|
if sym.IsConst() {
|
|
varName = sym.FullName() // Preserve name for documentation
|
|
isVar = false
|
|
return
|
|
}
|
|
|
|
// It's a variable
|
|
varName = sym.FullName()
|
|
isVar = true
|
|
return
|
|
}
|
|
|
|
// Not a variable, must be an expression
|
|
val, evalErr := utils.EvaluateExpression(param, constLookup)
|
|
if evalErr != nil {
|
|
err = fmt.Errorf("not a valid variable or expression: %w", evalErr)
|
|
return
|
|
}
|
|
|
|
if val < 0 || val > 65535 {
|
|
err = fmt.Errorf("value %d out of range (0-65535)", val)
|
|
return
|
|
}
|
|
|
|
value = uint16(val)
|
|
isVar = false
|
|
return
|
|
}
|