                                LFN-QBasic

    This is the explainations to LFNBASIC.BAS, the subroutines are 7,212
bytes is size WITHOUT the DECLARE's or including any REM's in the file, so
rather than make it bigger, I created this file for those who wish the read
about how the nitty gritty details of the programming is done. To those who
like to have spaces indenting subsections of code for clairity, even that is
missing, them there spaces added several kilos to the result, and some of
you may have tight packages already. You can reduce the package size
further, matter of fact I did that, the result is much smaller, but harder
to read. I do recommend that you leave the major function names alone unless
you're really good at reading codified names.. You may want to refine my
reduction further, I use LFNxx's as the replacements, and remove all
subfunctions not used in the final product.

For examples:

No need of  RANDOM or APPEND?
    Edit LFNOPEN and remove the subroutines called by the function.

All work done in current directory?
    No need to have LFNDOSPATH and it related sub functions taking up space.

Never change directories?
    Remove LFNCHDIR

Never create new files?
    Remove LFNNAMENEW and edit all the opens which call upon it.

Never use long filenames?
    Why have this package?

    Bear in mind that some routines are shared by other parts of the
package, for example LFNEXIST is used frequently as a check to prevent error
messages about files which don't exist. While you could rely on QBasic to do
this job, I prefered to catch this possibility before it happened. You can
also save some space by collapsing certain parts of the code because in
several places I use a subroutine to contain code which may only be used by
that routine for ease of reading. And in other places I use a subroutine for
code which can be done 'inline', like QUOTE$.

---------------------------------------------------------------------------

    TYPE dos
    full AS STRING * 128
    real AS STRING * 95
    used AS STRING * 1
    END TYPE

    The subroutines rely on a structure to hold the filenames. I could've 
used separate arrays for the system, but it is actuallly easier this way,
though it does tend to be a little memory intensive. DOS.FULL is for the
Long FileName, DOS.REAL is for the DOS8.3 name, and DOS.USED is an indicator
that the entry is in use. LFNOPEN set this flag, and LFNCLOSE clears it.
Should the program be called upon to open a file already in use,
LFNERROR(155) is called.

---------------------------------------------------------------------------

    DIM SHARED lfn(1 TO 3) AS dos
    COMMON SHARED errnum, lfnmax, true, false
    ON ERROR GOTO errhand
    true = -1: false = 0: lfnmax = 3
    FOR i = 1 TO lfnmax: lfn(i).used = " ": NEXT i

    The array is called LFN, for most applications three entries is enough,
I have one program which does a recursive directory search which uses as
many as eight. ERRNUM, TRUE, FALSE are carry-overs, you DON'T need to COMMON
them for the subroutines to work, but ERRNUM is usefull because you can use
it in the ERRHAND to be set to the ERR and find out what happened to cause
the error. LFNMAX does need to be GLOBAL because otherwise the subrroutine
will generate a SUBSCRIPT OUT OF RANGE error if you use LFNOPENS a fourth
time.  Also, the code about going to the ERROR handling routine needs to be
here.  The only other piece of initialization is that LFN(x).USED needs to
be set to CHR$(32). This is primarily for checking the used status of the
entry.  This is detailed in LFNOPEN.

---------------------------------------------------------------------------

    testlfn

    Main Entry Point for demo. (Or another way of putting this, YOUR CODE
HERE.) The actuall code follows ERRHAND, and most of the code detailed in
the file does NOT follow the manner in which QBasic stores code. Rather it's
detailed in a logical progression.

    errhand:
    FOR i = 1 TO lfnmax: lfnclose (i): NEXT i
    PRINT ERR: BEEP: STOP
    RESUME NEXT

    The error handling is needed because of one small minor detail which is
explained further in LFNNAMENEW, all the LFNFILES should be closed if you 
don't need to do any other error processing functions. The RESUME is needed 
by QBasic, else you get an error for excluding it, and it's also handy to 
step back to the point where the error happened. That way you can find the 
offending statement.

---------------------------------------------------------------------------

    SUB testlfn

    functions called        LFNCHDIR
                            LFNOPEN
                            LFNOPENS
                            LFNCLOSE
                            LFNCLOSEALL

    lfnchdir ("C:\Program Files\123 Free Solitaire")
    file1 = lfnopen("another readme.txt", "OUTPUT", 1, 0)
    PRINT #1, "Fresh Text " + TIME$ + " " + DATE$
    lfnclose (1)
    file2 = lfnopen("D:\Program Files\Buttonz
        & Tilez\readme.txt", "INPUT", 2, 0)
    file1 = lfnopens("another readme.txt", "INPUT")
    INPUT #file2, a$
    PRINT a$
    INPUT #file1, a$
    PRINT a$
    lfncloseall
    SYSTEM
    END SUB

    These are some very simple examples because once you get through all the
nitty-gritty details of the sub-routines, you don't have to really know more
than this:

    file=LFNOPEN("[path\]filename.ext","mode",which,size)
    file=LFNOPENS("[path\]filename.ext","mode")
    LFNCLOSE(file)
And
    LFNCHDIR("path")

    Beyond this, it gets complex as you wade through all the code which is
used to make this seem like such a simple task to access Long File Names
under QBasic.

    The only other routine you may want to use is 
LFNEXIST("[path\]filename.ext"), this will return TRUE if it find the file, 
or FALSE if the file doesn't exist.

    The only other requirement of the code is that [C:\TEMP] exists, if you
want to know more about these subroutines, read on, and if you have any
questions WRITE me.

    BTW: I realize that someone having the exact same setup as me is
astronomical, so if you want to use the examples, be sure to find some
places it can work with.

---------------------------------------------------------------------------

    SUB lfnchdir            (where AS STRING)

    functions called        QUOTE$
                            STRIPR$

    a$ = stripr$(where)
    SHELL "dir " + quote$(stripr$((a$))) + " >c:\temp\readit.txt"
    file = FREEFILE
    OPEN "c:\temp\readit.txt" FOR INPUT AS file
    FOR i = 1 TO 4: LINE INPUT #file, b$: NEXT i
    CLOSE file
    KILL "c:\temp\readit.txt"
    IF INSTR(b$, a$) = 0 THEN lfnerror (176)
    IF MID$(a$, 2, 1) = ":" THEN
        SHELL LEFT$(a$, 2)
        SHELL "chdir " + quote$(stripr$MID$(a$, 3)))
    ELSE
        SHELL "chdir " + quote$(stripr$(a$, 3))
    END IF
    END SUB

    First off, all the subroutines use your [C:\TEMP] directory to create a
file READIT.TXT for the purposes of verifing the presense of the intended
function, if you don't have a [C:\TEMP] then be sure to change this to a
valid directory. Not that it's serious, but I've found it handy to see what
ended up in the file just to see what the heck happened at times. Elsewhere
in the subroutines we use the SHELLing to find a file, here we take a
different twist. We're looking for the fourth line of a STANDARD directory
listing which contains the line: Directory of ...., and ensuring our a$
equal it. If not, LFNERROR(176), Path not found. If the line is there, then
we do a check for a drive letter by seeing if a ':' is in the second
position and doing a separate SHELL to swap drives before changing
directories. You'll see the function's QUOTE$ (and STRIPR$) pop up fairly
often within these routines, it's a darn sight easier on the eyes to read
what you're doing rather than CHR$(34)+a$+CHR$(34).

---------------------------------------------------------------------------

    FUNCTION lfnopen%        (file AS STRING
                            , mode AS STRING
                            , which AS INTEGER
                            , size AS INTEGER)

    functions called        LFNERROR
                            LFNIOAPPEND
                            LFNIOINPUT
                            LFNIOOUTPUT
                            LFNIORANDOM

    IF which = 0 THEN which = FREEFILE
    IF lfn(which).used <> " " THEN lfnerror (155)
    SELECT CASE (mode)
        CASE ("INPUT")
            a = lfnioinput(file, which)
        CASE ("OUTPUT")
            a = lfniooutput(file, which)
        CASE ("APPEND")
            a = lfnioappend(file, which)
        CASE ("RANDOM")
            a = lfniorandom(file, which, size)
        CASE ELSE
            lfnerror (154)
    END SELECT
    lfnopen = which
    END FUNCTION

    Here's the Beef! This is the core of the LFN functions, what you call on 
to access those LFN's without having to do any fancy yourself to get at the 
DOS8.3 name. All you need to do is replace all your normal: OPEN 
"filename.ext" FOR mode AS which with:

LFNOPEN("Long Filename.extlong", "mode", which, size)

    and not worry about a Bad Filename Crash. One of the handier sides of
this function is that should you decide to use it, it provides a 'C' type of
ability and if it's passed a 0 as the WHICH, if loads WHICH with FREEFILE,
and because is passes WHICH back to the caller, you can ignore the file
number and rely on the stacking nature to keep overlapping filenumbers from
happening. If you do need to have a definate file open for some reason, open
it first via the subroutine or using the OPEN yourself. My apologies, I
didn't provide a BINARY mode, I've never used it myself, but it's easy to
add once you see the subroutines. I also don't provide for the ACCESS or
LOCK statements because: A: I haven't done any programming in need of it,
therefore I've never used it myself.  B: All my programs run under windows
which implicately LOCKS any file opened by a DOS window. If you don't know
about this one, open a file with QBasic, open the same file with NotePad,
modify with notepad, and try to save. If filelocking is working correctly on
your machine, you'll get an error.  In any case, that's easy to add should
your programming need it, it'll make the subroutine call bigger.

---------------------------------------------------------------------------

    FUNCTION lfnopens%    (file AS STRING
                          , mode AS STRING)

    functions called      LFNOPEN

    lfnopens = lfnopen(file, mode, 0, 0)
    END FUNCTION

    Which is why I have a shorter call LFNOPENS, all you need to provide it
is a MODE and (of course) the filename. It calls LFNOPEN and provide Zero's
for WHICH and SIZE, while SIZE is only needed for RANDOM, LFNOPEN was
designed to accomadate it as well. LFNOPENS returns WHICH to the caller as
well.

---------------------------------------------------------------------------

    SUB lfnclose        (which AS INTEGER)

    functions called    LFNRENAME

    IF lfn(which).used = " " THEN EXIT SUB
    CLOSE which
    IF lfn(which).used = "*"
        THEN a = lfnrename (lfn(which).real, lfn(which).full)
    lfn(which).used = " "
    END SUB

    LFNCLOSE is needed for two reasons:
        A: the LFN().USED flag needs to be cleared.
        B: New filenames need to be converted to their LFN's

    The New Filenames are explained further in the function LFNRENAME.  
While it's provided as separate function, is short so if you want to, the
function call can be replaced with the lines from the subroutine.  Unless
you need to do any LFN renaming.

---------------------------------------------------------------------------

    SUB lfncloseall

    functions called    LFNCLOSE

    FOR i = 1 TO lfnmax: lfnclose (i): NEXT i
    END SUB

    This is primarily for quickly closing all your LFN's, it doesn't even
check if there's any open.

---------------------------------------------------------------------------

    FUNCTION lfnrename  (oldname AS STRING
                        , newname AS STRING)

    functions called    QUOTE$
                        STRIPR$

    a$ = stripr$(newname)
    IF INSTR(a$, "\") THEN
        DO
            ix = INSTR(a$, "\")
            IF ix = 0 THEN EXIT DO
            a$ = MID$(a$, ix + 1)
        LOOP
    END IF
    SHELL "rename " + quote$(stripr$(oldname)) + " "
        + quote$(stripr$(a$))
    END FUNCTION

    This is faily straightforward, we're just calling upon DOS to do our
renaming. The flaw is that QBasic pads it's structure().strings with
righthand spaces, so the function STRIPR$ is needed to remove them before
wrapping the quotes around the filename. And while I have not run into any
errors wrapping the DOS8.3 names in quotes, I may not have used the function
under all possible filenames. One thing that must be done is the stripping
of any possible path from the NEWNAME otherwise rename thinks you're trying
to rename across directories and/or drives.

    What I call the FATAL FLAW needs to be explained: LFN-QBasic generates 
unique filenames for any NEW files you open. Should the program abort in any
means which does not allow it to properly convert these names to their LFN
equivalents, you'll end up with 'Mystery Files'.

---------------------------------------------------------------------------

    FUNCTION lfnioappend%   (file AS STRING
                            , which AS INTEGER)

    functions called        LFNEXIST
                            LFNNAMENEW
                            LFNNAMEOLD
                            STRIPR$

    IF lfnexist(file) THEN
        a = lfnnameold(file, which)
    ELSE
        a = lfnnamenew(file, which)
    END IF
    OPEN stripr$(lfn(which).real) FOR APPEND AS which
    END FUNCTION

    Of these next four subroutines, three are almost exactly alike. Matter
of fact, even the fourth looks like those three except it doesn't have the
check on whether it should call upon LFNNAMENEW, that one is LFNIOINPUT
because, of course, the file must exist for reading. APPEND and OUTPUT, as
well as RANDOM, create the file if is doesn't exist, and only RANDOM nees
the SIZE parameter. If you want, you can directly call these and bypass
LFNOPEN routine, I built that primarily to have a central access to these
without having to type in a different routine name for each open. The
LFNNAME---'s are detailed following this section.  This, (of course), is 
APPEND, opens a file for appending to. Otherwise it's like LFNIOOUTPUT.

---------------------------------------------------------------------------

    FUNCTION lfnioinput%    (file AS STRING
                            , which AS INTEGER)

    functions called        LFNEXIST
                            LFNNAMEOLD
                            STRIPR$

    IF NOT lfnexist(file) THEN lfnerror (164)
    a = lfnnameold(file, which)
    OPEN stripr$(lfn(which).real) FOR INPUT AS which
    END FUNCTION

    Only one which will generate an LFNERROR(164) should the file not exist,
all the LFNERROR's are mostly the standard QBasic codes+100. So 164 means 
Bad File Name, Punish it! ;)

---------------------------------------------------------------------------

    FUNCTION lfniooutput%   (file AS STRING
                            , which AS INTEGER)

    functions called        LFNEXIST
                            LFNNAMENEW
                            LFNNAMEOLD
                            STRIPR$

    IF lfnexist(file) THEN
        a = lfnnameold(file, which)
    ELSE
        a = lfnnamenew(file, which)
    END IF
    OPEN stripr$(lfn(which).real) FOR OUTPUT AS which
    END FUNCTION

    I suppose I could've written a more complex LFNOPEN setup and done away
with these subs, but they actually expand the system. You can add you own
unique LFNOPEN's by expanding the basic SELECT CASE.

    All the file functions check to see if the files exists so the routine
can determine whether to call upon the subroutine which retrieves the DOS8.3 
name, or call upon the newname generator.

---------------------------------------------------------------------------

    FUNCTION lfniorandom%   (file AS STRING
                            , which AS INTEGER
                            , size AS INTEGER)

    functions called        LFNEXIST
                            LFNNAMENEW
                            LFNNAMEOLD
                            STRIPR$

    IF lfnexist(file) THEN
        a = lfnnameold(file, which)
    ELSE
        a = lfnnamenew(file, which)
    END IF
    OPEN stripr$(lfn(which).real) FOR RANDOM AS which LEN = size
    END FUNCTION

    The RANDOM OPEN is the only one which uses the SIZE parameter of
LFNOPEN, like I said, I've never used the BINARY mode because generally when 
I access a file for direct byte access, I use RANDOM and get at big chunks, 
check out my GIFSIZE program. This is the only function with which you can't 
call LFNOPENS because then you'll end up with a size of ZERO!

---------------------------------------------------------------------------

    FUNCTION lfnnameold%    (file AS STRING
                            , which AS INTEGER)

    functions called        LFNDOS$

    lfn(which).full = file
    lfn(which).real = lfndos$(file)
    lfn(which).used = "!"
    END FUNCTION

    This is primarily an assigner routine, it calls upon LFNDOS to retrieve
the DOS8.3 filename. I suppose LFNDOS could've been included here, but I had 
my reasons for making it a separate routine for debugging this system and 
haven't really determined if I should tie them together.

---------------------------------------------------------------------------

    FUNCTION lfnnamenew%    (file AS STRING
                            , which AS INTEGER)

    functions called        LFNDOSPATH$
                            STRIPR$

    STATIC old$
    IF INSTR(file, "\") THEN p$ = lfndospath$(file)
    lfn(which).full = stripr$(file)
    lfn(which).used = "*"
    DO
        a$ = "tt" + LEFT$(TIME$, 2) + MID$(TIME$, 4, 2)
            + RIGHT$(TIME$, 2) + "."
            + CHR$(which + 65) + "lf"
        IF a$ <> old$ THEN EXIT DO
    LOOP
    old$ = a$
    lfn(which).real = p$ + a$
    END FUNCTION

    I use a unique means for generating a filename for new files, I use the
current TIME$ to create a filename which is different for each instance. All
the filenames start with 'TT', use the hours, minutes, and seconds, and have
the extension of the filenumber+65 to generate an letter. This does have a
limit, you can only generate ONE filename for each filenumber every second,
and without making the code more complex, it doesn't check different
extensions, though that shouldn't happen because you MUST close the
filenumber before using it again. The only other exception is if you include
a PATH to the file in question, then the subroutine calls upon another
subroutine to get the DOS8.3 pathname, that that get's a little crazy,
you'll see why in a minute because while LFNNAMEOLD doesn't do that, it
calls upon LFNDOS, which does.

---------------------------------------------------------------------------

    FUNCTION lfndos$        (item AS STRING)

    functions called        LFNDOSPATH$
                            STRIPR$

    a$ = item
    IF INSTR(a$, "\") THEN p$ = lfndospath$(a$)
    a$ = lfndosname$(a$)
    lfndos$ = p$ + a$
    END FUNCTION

    While this is only called upon by LFNNAMEOLD, and like I said, could be
included there, when I was debugging this system, I found it handier to make
it separate. Like LFNNAMENEW, it checks for the presence of a path by the
simple fact that paths have a '\' in them. I don't know if you can do an
OPEN "d:filename.ext", I don't program like that, I always include the
backslash. Otherwise the routine calls LFNDOSNAME to retrieve the DOS8.3
name to be returned to the caller, and returns the PATH$ (if any) plus the
returned name.

---------------------------------------------------------------------------

    FUNCTION lfndosname$    (item AS STRING)

    functions called        QUOTE$
                            STRIPR$

    a$ = item
    SHELL "dir " + quote$(stripr$(item)) + " >c:\temp\readit.txt"
    file = FREEFILE
    OPEN "c:\temp\readit.txt" FOR INPUT AS file
    FOR i = 1 TO 5: LINE INPUT #file, a$: NEXT i
    LINE INPUT #file, a$
    CLOSE file
    KILL "c:\temp\readit.txt"
    a$ = stripr$(LEFT$(a$, INSTR(a$, " ") - 1) + "." + MID$(a$, 10, 3))
    lfndosname$ = a$
    END FUNCTION

    If you've ever taken time to examine the directory listings produced
under Win95DOS, they all have something in common, (at least mine does, I'm
not sure this holds true for EVERY Win95 installation), five lines of header
information, followed by the listing itself. This does a simple SHELL
directory call providing the stripped and quoted filename to the call,
piping the result into READIT.TXT. This is opened using the FREEFILE
function to avoid filenumber conflicts, the first five lines are bypassed,
then the filename line is read. We then close the file, kill it, and
decypher the line we just read. There is two possible errors which I haven't
run across because I never call this directly.

    A: File Not Found - It was passed a non-existant file.

    B: <DIR> - it was passed a filename which happens to be a
               directory name.

    In both cases it will return a string which does not reflect what you
wanted, and cause a crash.

---------------------------------------------------------------------------

    FUNCTION lfndospath$    (item AS STRING)

    functions called        LFNDOSHUNT$
                            LFNDOSTEST
                            LFNDOSSEARCH$
                            LFNERROR

    undone$ = lfndoshunt$(item)
    IF lfndostest(item) THEN
        ix = INSTR(undone$, "\")
        done$ = LEFT$(undone$, ix - 1)
        hunt$ = done$
        undone$ = MID$(undone$, ix)
        path$ = lfndossearch$(hunt$, "")
    ELSE
        ix = INSTR(undone$, "\")
        done$ = done$ + LEFT$(undone$, ix)
        undone$ = MID$(undone$, ix + 1)
        ix = INSTR(undone$, "\")
        IF ix = 0 THEN lfnerror(187)
        hunt$ = LEFT$(undone$, ix - 1)
        path$ = done$ + path$ + lfndossearch$(hunt$, done$) + "\"
    END IF
    DO
        ix = INSTR(undone$, "\")
        IF ix = LEN(undone$) THEN EXIT DO
        done$ = done$ + LEFT$(undone$, ix)
        undone$ = MID$(undone$, ix + 1)
        ix = INSTR(undone$, "\")
        IF ix = 0 THEN lfnerror(187)
        hunt$ = LEFT$(undone$, ix - 1)
        path$ = path$ + lfndossearch$(hunt$, path$) + "\"
    LOOP
    lfndospath$ = path$
    END FUNCTION

    Things get a little hectic when you pass a path\filename to these
functions, because the path needs to be converted to its DOS8.3 name as 
well, and this can't be done directly. At least in every method I've tried. 
So the only method which I've come up with which works every time is to do a 
DIR of the preceeding directory, and scan for the LFN starting in column 45 
to equal the subdirectory name I'm hunting for, and get the DOS8.3 name out 
of the left-hand columns.

    First thing done is: Strip The FileName Off!. Because I pass the entire
path\filename to the function.

    UNDONE$ is the keeper of the path to be scanned.
    DONE$ holds 'unconverted' path which has been done.
    HUNT$ is the directory we're looking for.
    PATH$ is the converted path.

    One of the things which need to be checked is whether we're starting in
the current directory, or from elsewhere. If you take time to think about
it, you can't use the same starting sequence for both because of the way the
path works. Starting from any other directory other than the current
directory involved having a '\', 'D:\', or '..\' prefixing the path. Yes, I
know, '..\' does start in the current directory, but not the way the path
works. This is Legal:

'D:\directory\subdirectory\..\otherdirectory\filename.ext'

    So can be started in the same manner as we use in starting from the
root, or drive:root combination. But when we use a directory off the
current, we need to scan the current first to GET our starting point and it
has to be scanned by not passing LFNDOSSEARCH the 'start path' parameter. On
the other hand, we do need to pass LFNDOSSEARCH a 'start path' to get the
correct starting DOS8.3 filename in the other three cases, and the 'start
path' needs to be part of the resulting translation.

    After getting started, it's a simple matter to scan from left to right
until we run out of UNDONE path, then passing the resulting translation back
to the caller. The only possible problem would be a bad path somehow gets
here, resulting in a parsing problem, LFNERROR(187) is one of two error
especially for LFN's, called 'Path irregularity'.

---------------------------------------------------------------------------

    FUNCTION lfndoshunt$    (item AS STRING)

    functions called        none

    a$ = item
    DO
        ix = INSTR(a$, "\")
        IF ix = 0 THEN EXIT DO
        b$ = b$ + LEFT$(a$, ix)
        a$ = MID$(a$, ix + 1)
    LOOP
    lfndoshunt$ = b$
    END FUNCTION

    Simple to explain, scan until you run out of '\'s, and pass everything
from here left back.

---------------------------------------------------------------------------

    FUNCTION lfndossearch$  (item AS STRING
                            , where AS STRING)

    functions called        LFNERROR

    item = UCASE$(item)
    SHELL "dir " + where + "*. >c:\temp\readit.txt"
    file = FREEFILE
    OPEN "c:\temp\readit.txt" FOR INPUT AS file
    FOR i = 1 TO 5: LINE INPUT #file, a$: NEXT i
    DO
        LINE INPUT #file, a$
        IF ASC(a$) = 32 THEN lfnerror (186)
        IF UCASE$(MID$(a$, 45)) = item THEN EXIT DO
    LOOP
    CLOSE file
    KILL "c:\temp\readit.txt"
    b$ = LEFT$(a$, INSTR(a$, " ") - 1)
    IF ASC(MID$(a$, 10)) <> 32
    THEN b$ = b$ + "." + stripr$(MID$(a$, 10, 3))
    lfndossearch$ = b$
    END FUNCTION

    This is the scanning routine and we do it by simply blasting the
contents of the previous directory into READIT.TXT and looking for our 
match. As far as I've been able to determine, you can't have the same name 
twice, regardless of capitalization, try it sometimes. So to avoid the 
possibliliy of passing up the name, all comparisons are done in UCASE. Like 
most of the other SHELL dir call's, we bypass the first five lines, and 
commence with looking for the LFN directory at line six. You will note that 
when we called this routine, we passed it the current translated path, if 
any, for it to work with. Namely because you put quotes around [*.], you get 
different results than without. Try that sometime too. The only possible 
error from here is that it's a bad directory name it's looking for, and 
rather than hitting a EOF error, we check for a space as being the first 
character in the directory line, also part of a standard listing. We find 
that, we produce LFNERROR(186), 'Couldn't resolve path'. Otherwise the found 
name is extracted from the left-hand side just like the filename resolver, 
except if column 10 contains anything BUT a space. Then it adds the '.3' 
extension to the directory.

    BTW, in case you haven't figured it out from the previous use of the
command, the dos directory listing always has a space at column 9, but we 
want everything before the FIRST space in the line.

---------------------------------------------------------------------------

    FUNCTION lfndostest%    (item AS STRING)

    functions called        none

    IF LEFT$(item, 1) = "\" THEN EXIT FUNCTION
    IF LEFT$(item, 3) = "..\" THEN EXIT FUNCTION
    IF MID$(item, 2, 1) = ":" THEN EXIT FUNCTION
    lfndostest = true
    END FUNCTION

    This is just a minor routine used by LFNDOSPATH to determine whether
it's staring from the current directory or not. The easy way to describe
this is:

    IF NOT (one of these three checks) RETURN FALSE

---------------------------------------------------------------------------

    FUNCTION lfnexist%      (which AS STRING)

    functions called        QUOTE$
                            STRIPR$

    a$ = which
    errnum = false
    SHELL "dir /b/v " + quote$(stripr$(a$)) + " >c:\temp\readit.txt"
    file = FREEFILE
    OPEN "c:\temp\readit.txt" FOR INPUT AS file
    IF EOF(file) THEN errnum = true
    CLOSE file
    KILL "c:\temp\readit.txt"
    IF errnum THEN lfnexist% = false ELSE lfnexist% = true
    END FUNCTION

    I'm sure some of you have made some form of a EXIST function before, but
doing it under LFNQBasic has it's own problems. First off, you need the 
DOS8.3 name to execute a RENAME test, and SHELLing to DOS and asking for it 
to do a RENAME test leads to the problem, How Do You Get The Error 
Message?!? Well, this does the job with the SHELL dir action, using the 
'/B/V' switches for

    '/B' Uses bare format (no heading information or summary).
    '/V' Verbose mode.

    So that all we will end up with is either one of two results:

    A: file exists, there is a line in READIT.TXT
    B: file doesn't exists, READIT.TXT is empty.

    Rather that try to read the file and seeing if we generate an error, pop
open the file and see if the first thing we get is EOF(file) = TRUE and 
follow through with setting the ERRNUM flag as needed. BTW, while this is 
the only place ERRNUM is used, I made it GLOBAL for debugging reasons.

---------------------------------------------------------------------------

    SUB lfnerror            (what AS INTEGER)

    functions called        LFNCLOSE

    PRINT "LFN Error: ";
    SELECT CASE (what)
    CASE (154)
        PRINT "Bad file mode"
    CASE (155)
        PRINT "File already open or name already used"
    CASE (176)
        PRINT "Path not found or path/file access error"
    CASE (164)
        PRINT "File not found"
    CASE (178)
        PRINT "General catchall error message"
    CASE (186)
        PRINT "Couldn't resolve path"
    CASE (187)
        PRINT "Path irregularity"
    END SELECT
    PRINT "Active files:"
    FOR i = 1 TO lfnmax
        IF ASC(lfn(i).used) <> 32 THEN PRINT i; lfn(i).full
        lfnclose (i)
    NEXT i
    BEEP
    STOP
    END SUB

    Here we do two things, print the LFN error message, and close all the
open files. Like the ERRHAND up top, if you want to do any sort of 'live' 
work, you will need to adjust this so that it DOESN'T close the files, but 
do remember that if you create any NEW files, they ahve that unique 
filename, and you may find yourself with a directory full of TThhmmss.l's. I 
did when I first started working out the details of these routines, more 
that 300 of them!

    BTW: I could've called LFNCLOSEALL, but for debugging reasons I wanted
to include a system where it printed all open files at the same time.

---------------------------------------------------------------------------

    FUNCTION quote$         (item AS STRING)
    
    functions called        none

    IF ASC(item) <> 34
    THEN item = CHR$(34) + item + CHR$(34)
    quote$ = item
    END FUNCTION

    While this could've been elimated and code straight into the routines, 
it does keep the codelines shorter so that it's easier to read. Besides 
which, I use it in other programs.

---------------------------------------------------------------------------

    FUNCTION stripl$        (item AS STRING)

    functions called        none
    
    DO
        IF item = " " THEN EXIT DO
        IF ASC(LEFT$(item, 1)) <> 32 THEN EXIT DO
        item = MID$(item, 2)
    LOOP
    stripl$ = item
    END FUNCTION

    This is extra, it's not needed.

    It's included as a compliment to STRIPR.

---------------------------------------------------------------------------

    FUNCTION stripr$        (item AS STRING)

    functions called        none
    
    DO
        IF item = " " THEN EXIT DO
        IF ASC(RIGHT$(item, 1)) <> 32 THEN EXIT DO
        item = LEFT$(item, LEN(item) - 1)
    LOOP
    stripr$ = item
    END FUNCTION

    This is needed because QBasic pads it's TYPE strings with spaces, and
you can't just use the first space as a kill from here point because it may
be part of the filename. It was just as easier to do it like this as in a
FOR:NEXT loop, either one would work, and cause a crash when passed a
ALL-SPACE string. Hence the check for a single space left in the string
before the test. It could be extended to return a null string if that
happens, 'snot hard to do.

---------------------------------------------------------------------------

    There you have it, LFN-QBasic.  After doing this documentation, I have 
gained a new level of symphony for people who have to document other 
people's programs, I've found doing my own hard enough, and I KNOW what the 
program does! ;)

God Bless you and Enjoy!
