From 49566de7fbea2356b1b11075e76ffe9289ed1261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 5 Aug 2026 18:52:00 +0200 Subject: [PATCH] feat(asm): add did-you-mean suggestions for undefined labels Assisted-by: DeepSeek V4 Pro --- asm/riscv_assemble.go | 72 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index a14f4e7..6999168 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -162,7 +162,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv target := labelFromOperand(ops[0]) targetOff, ok := offsets[target] if !ok { - return nil, fmt.Errorf("undefined label %q", target) + return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets)) } offset := int32(targetOff - pc) // AUIPC X1, upper 20 bits @@ -187,7 +187,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv } targetOff, ok := offsets[target] if !ok { - return nil, fmt.Errorf("undefined label %q", target) + return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets)) } offset := int32(targetOff - pc) // C.J: funct3=0x5, offset in ±2 KB, bit 0 must be 0. @@ -208,7 +208,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv } targetOff, ok := offsets[target] if !ok { - return nil, fmt.Errorf("undefined label %q", target) + return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets)) } offset := int32(targetOff - pc) // JAL X0, target → C.J when offset fits. @@ -435,7 +435,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv target := labelFromOperand(ops[2]) targetOff, ok := offsets[target] if !ok { - return nil, fmt.Errorf("undefined label %q", target) + return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets)) } offset := int32(targetOff - pc) if rs1 < 0 || rs2 < 0 { @@ -1064,3 +1064,67 @@ func labelFromOperand(op *ast.Operand) string { } return op.Raw } + +// suggestLabel returns a "did you mean" suggestion for an undefined label. +func suggestLabel(target string, offsets map[string]int) string { + if len(offsets) == 0 { + return "" + } + // Find the closest matching label using Levenshtein distance. + bestDist := len(target) + 1 + var best string + for name := range offsets { + dist := levenshtein(target, name) + if dist < bestDist { + bestDist = dist + best = name + } + } + // Only suggest if the distance is small enough. + if bestDist <= 3 && bestDist < len(target)/2+1 { + return fmt.Sprintf(" — did you mean %q?", best) + } + return "" +} + +// levenshtein computes the Levenshtein distance between two strings. +func levenshtein(a, b string) int { + la, lb := len(a), len(b) + if la == 0 { + return lb + } + if lb == 0 { + return la + } + // Create a matrix of distances. + prev := make([]int, lb+1) + curr := make([]int, lb+1) + for j := 0; j <= lb; j++ { + prev[j] = j + } + for i := 1; i <= la; i++ { + curr[0] = i + for j := 1; j <= lb; j++ { + cost := 1 + if a[i-1] == b[j-1] { + cost = 0 + } + curr[j] = min3(curr[j-1]+1, prev[j]+1, prev[j-1]+cost) + } + prev, curr = curr, prev + } + return prev[lb] +} + +func min3(a, b, c int) int { + if a < b { + if a < c { + return a + } + return c + } + if b < c { + return b + } + return c +}