AFAIK, many BASIC implementations worked in-place: each input line was replaced by its tokenization, then by the bytecode, then the labels would be resolved. Consequently, printing the program listing was not a matter of simple "print all earlier input lines" (those are gone), it involved actual pretty-printing.
In fact, replacing the program text with its tokenization as the first compilation step was a very popular strategy back in the day, due to memory constraints. IIRC the early IBM FORTRAN compilers, being quite large (they performed quite a lot of optimizations), would not even fit into the core memory together with the source code of any reasonably useful program. So they were instead written as a series of passes (about fifty or so, I believe) each of which took the output of the previous pass and transformed it into the input for the next one; the tokenizer was quite tiny but it freed lot of space (text is quite redundant) so the next passes had room for the auxiliary structures and could themselves be larger, too.
In fact, replacing the program text with its tokenization as the first compilation step was a very popular strategy back in the day, due to memory constraints. IIRC the early IBM FORTRAN compilers, being quite large (they performed quite a lot of optimizations), would not even fit into the core memory together with the source code of any reasonably useful program. So they were instead written as a series of passes (about fifty or so, I believe) each of which took the output of the previous pass and transformed it into the input for the next one; the tokenizer was quite tiny but it freed lot of space (text is quite redundant) so the next passes had room for the auxiliary structures and could themselves be larger, too.