I can't speak to BASIC09 specifically, but there's quite a difference between an "i-code" and a tokenized form.
At a minimum, the i-code, in the end, need not look at all like the source code, whereas the tokenized form effectively IS the source code.
Many BASICs, when you type LIST, simply parrot back out the tokenized data in long form. With i-code, you can't do that.
Another example is if you look at the DEC BASICs. They used "statement modifiers".
10 SUM10 = SUM10 + A(I) IF A(I) > 10 FOR I = 1 TO 10
While this could be represented in tokenized form (in order to regenerate the source line), it can not be easily interpreted executed from that tokenized form.
Token based runtimes, much like i-code, simply take what they see and execute it in the context their in. But you can easily see the runtime encountering the
SUM10 = SUM10 + A(I) with out knowing that it should do so yet (due to the IF statement) or even that I is properly in range or not (since it's not even been initialized properly yet).
Sure, you could jump through hoops. Put in "invisible tokens" or whatever specifically to support this kind of construct, but in the "every byte counts" of vintage BASICs, that's a lot of work for not a lot of value. But since the DEC BASICs WERE compiled, rather than just interpreted raw, the system was able to take statements like this and rewrite them internally to be more "normal".
10 A = 1 + 2 * 3 / 2 + 4 * SQR(2)
In a normal tokenized system, the expression runtime has to account for the operator precedence AT runtime to properly evaluate this expression. Having to do the multiplication and such before the additions, etc.
Whereas in a compiled form, it can be reduced to an RPN style where the evaluator doesn't have to worry about that any more. This makes the runtime faster, since it's much simpler.
1 2 3 * 2 / 4 SQR(2) * + +