I have a variant object with 7 different kinds that I want to do some operations on (eg. addition, multiplication) but it becomes really ugly as I have to nest two case statements and end up with 49 branches in the end and that's really ugly. Here's what it looks like currently when I only populate one of the nested case statements:
case a.kind
  of symNumber:
    case b.kind
      of symNumber:
        discard
      of symSymbol:
        discard
      of symFunc:
        discard
      of symMul:
        discard
      of symAdd:
        discard
      of symPow:
        discard
      of symSpecial:
        discard
  of symSymbol:
    discard
  of symFunc:
    discard
  of symMul:
    discard
  of symAdd:
    discard
  of symPow:
    discard
  of symSpecial:
    discard
Most of the operations is independent on which order the parameters are passed in so those would only require one of the branches but some of them requires both. In the future if I want to add a new kind I have the advantage of knowing exactly where I have to add it but there are so many places that I'd have to change. It feels like there should be a "magic macro"-way to handle this more efficiently or more compactly at least. Have anyone else had this inconviniece before and even found a good solution to it? :D
type
  Kind1 = enum
    k1First
    k1Second
    k1Third
  
  Kidn2 = enum
    k2First
    k2Second
    k2Third
case (k1First, k2Second):
  of (in {k1First, k1Second}, _):
    echo "123"oh I had a similar problem when decoding powerpc instructions. What I ended up with was a macro which among other things "unrolled" the branches using a table. This would look kind off like this for your example:
const offsets: array[Sym, int] = [ord(high(Sym))+1, 0, 0, #[ecetera]#]
case ord(a.kind) + offsets[a.kind]
# all the cases which can be decoded based on the first part
of 0: discard
of 1: discard
# …
of ord(high(Sym))+1: # when a.kind == symNumber and b.kind == symNumber:
  discard
of ord(high(Sym))+2: # when a.kind == symNumber and b.kind == symSymbol:
  discard
Another advantage this brought me was that I could use computed goto for the interpreter.