If you are tight on space, you should hand-code a recursive descent parser; these are essentially LL(1) parsers. This is especially effective for langauges which are as "simple" as Basic. (I did several of these back in the 70s!). The good news is these don't contain any library code; just what you write.
They are pretty easy to code, if you already have a grammar.
First, you have to get rid of left recursive rules (e.g., X = X Y ).
This is generally pretty easy to do, so I leave it as an exercise.
Then if you have BNF rule of the form:
X = A B C ;
create a subroutine for each item in the rule (X, A, B, C) that returns a boolean
saying "I saw the corresponding syntax construct". For X, code:
subroutine X()
if ~(A()) return false;
if ~(B()) { error(); return false; }
if ~(C()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end X;
Similarly for A, B, C.
If a token is a terminal, write code that checks
the input stream for the string of characters that makes up the terminal.
E.g, for a Number, check that input stream contains digits and advance the
input stream cursor past the digits. This is especially easy if you
are parsing out of a buffer (for BASIC, you tend to get one line at time)
by simply advancing or not advancing a buffer scan pointer.
This code is essentially the lexer part of the parser.
If your BNF rule is recursive... don't worry. Just code the recursive call.
This handles grammar rules like:
T = '(' T ')' ;
This can be coded as:
subroutine T()
if ~(left_paren()) return false;
if ~(T()) { error(); return false; }
if ~(right_paren()) { error(); return false; }
// insert semantic action here: generate code, do the work, ....
return true;
end X;
If you have a BNF rule with an alternative:
P = Q | R ;
then code P with alternative choices:
subroutine P()
if ~(Q)
{if ~(R) return false;
return true;
}
return true;
end;
Sometimes you'll encounter list forming rules.
These tend to be left recursive, and this case is easily handled.
Example:
L = A | L A ;
You can code this as:
subroutine L()
if ~(A()) then return false;
while (A()) do // loop
return true;
end;
You can code several hundred grammar rules in a day or two this way.
There's more details to fill in, but the basics here should be more than enough.
If you are really tight on space, you can build a virtual machine that implements
these ideas. That's what I did back in 70s, when 8K 16 bit words was what you could get.