Since subexpressions 4 and 5 are optional, the greedy second and third subexpressions end up gobbling everything, even if #else
appears in your input. You need to make them non-greedy. To ensure that subexpression 5 is only filled when subexpression 4 matches, put one inside the other. You'll end up with this:
/#(ifn?def) +(.*?)\n(.*?)(#else(.*))?(#endif)/si
I would make subexpression 4 a non-capturing group, and I wouldn't bother grouping subexpression 6 at all since the contents of both can be inferred by context.