diff --git a/internal/compiler/funchandler.go b/internal/compiler/funchandler.go index ee254e5..6920775 100644 --- a/internal/compiler/funchandler.go +++ b/internal/compiler/funchandler.go @@ -272,25 +272,57 @@ func (fh *FunctionHandler) HandleFuncCall(line preproc.Line) ([]string, error) { for i, arg := range callArgs { param := funcDecl.Params[i] - // Handle different argument types + // Handle special cases first if strings.HasPrefix(arg, "@") { // Label reference: @labelname if err := fh.processLabelArg(arg, param, funcName, line, &inAssigns); err != nil { return nil, err } - } else if strings.HasPrefix(arg, "\"") && strings.HasSuffix(arg, "\"") { + continue + } + + if strings.HasPrefix(arg, "\"") && strings.HasSuffix(arg, "\"") { // String constant if err := fh.processStringArg(arg, param, funcName, line, pragmaSet, &inAssigns); err != nil { return nil, err } - } else if sym := fh.symTable.Lookup(arg, fh.currentFuncs); sym != nil { - // Variable reference + continue + } + + // 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 + } + + _, _, value, isVar, err := ParseOperandParam( + arg, fh.symTable, fh.currentFuncs, constLookup) + if err != nil { + return nil, fmt.Errorf("%s:%d: CALL %s arg %d (%q): %w", + line.Filename, line.LineNo, funcName, i+1, arg, err) + } + + // Out/IO parameters must be writable variables + if param.Direction.Has(DirOut) && !isVar { + return nil, fmt.Errorf("%s:%d: CALL %s: cannot pass constant/expression to out/io parameter %q", + line.Filename, line.LineNo, funcName, param.Symbol.Name) + } + + if isVar { + // Variable - get full symbol for type checking + sym := fh.symTable.Lookup(arg, fh.currentFuncs) + if sym == nil { + return nil, fmt.Errorf("%s:%d: CALL %s: internal error - variable %q not found", + line.Filename, line.LineNo, funcName, arg) + } if err := fh.processVarArg(sym, param, funcName, line, &inAssigns, &outAssigns); err != nil { return nil, err } } else { - // Numeric constant - if err := fh.processConstArg(arg, param, funcName, line, &inAssigns); err != nil { + // Constant or expression result + if err := fh.processConstValue(value, param, funcName, line, &inAssigns); err != nil { return nil, err } } @@ -439,6 +471,34 @@ func (fh *FunctionHandler) processConstArg(arg string, param *FuncParam, funcNam return nil } +// processConstValue handles pre-computed constant/expression values +// Used after ParseOperandParam has already validated and computed the value +func (fh *FunctionHandler) processConstValue(value uint16, param *FuncParam, funcName string, line preproc.Line, inAssigns *[]string) error { + // Verify value fits in parameter type + if param.Symbol.IsByte() && value > 255 { + return fmt.Errorf("%s:%d: CALL %s: value %d out of byte range for parameter %q", + line.Filename, line.LineNo, funcName, value, param.Symbol.Name) + } + + lowByte := uint8(value & 0xFF) + highByte := uint8((value >> 8) & 0xFF) + + *inAssigns = append(*inAssigns, + fmt.Sprintf(" lda #%d", lowByte), + fmt.Sprintf(" sta %s", param.Symbol.FullName()), + ) + + if param.Symbol.IsWord() { + // Optimize: only reload A if high byte differs + if highByte != lowByte { + *inAssigns = append(*inAssigns, fmt.Sprintf(" lda #%d", highByte)) + } + *inAssigns = append(*inAssigns, fmt.Sprintf(" sta %s+1", param.Symbol.FullName())) + } + + return nil +} + // parseImplicitDecl parses {BYTE varname} or {WORD varname} and adds to symbol table func (fh *FunctionHandler) parseImplicitDecl(decl string, funcName string) error { parts := strings.Fields(decl)