4

Many dialects of 8-bit BASIC allow ‘crunched’ source; that is, most spaces can be ignored between keywords:

10 FORX=1TO10
20 PRINTX,X^2
30 NEXTX

After Microsoft BASIC-80 Rev. 5.x, however, spaces were required:

10 FOR X=1 TO 10
20 PRINT X,X^2
30 NEXT X

Other later BASIC interpreters (such as Locomotive BASIC on the Amstrad CPC) required spaces, while others didn't (for example BBC BASIC).

I'm looking for a program (Linux-capable preferred) that will reliably insert the necessary spaces into BASIC text. I'm not looking for the ability to detokenize: assume I have the text available.

8
  • 3
    So is this about converting sources? Do you got any specific BASIC in mind? After all, this depends much on the keywords valid. Can it be assumed, that it's correct BASIC code, so no ambiguities? If yes, some Perl regex may doe it.
    – Raffzahn
    Dec 9, 2019 at 22:53
  • 1
    Let's assume that the code is valid. Minimum target would be PET BASIC 4, though MSX BASIC would be ideal.
    – scruss
    Dec 9, 2019 at 23:02
  • 1
    Assuming valid code does help, except it would still depends much on the vocabulary. 10 LETA=NOTA would already give a headache in PET-BASIC - or worse, 10 A=BMODC which in PET BASIC would have to stay like that, while as MSX it needs to be turned into 10 A = B MOD C. I guess it would need to be a tool tuned to a certain dialect and work according to this dialects keyword list. Not to mention the classic GO TO vs. GOTO (Oh, and no, I do not know any existing generic tool. I'd just sit down and sketch up a bunch of Perl)
    – Raffzahn
    Dec 9, 2019 at 23:35
  • 4
    It seems that it'd be easier to write a pretty-printer if you did have the tokenized form rather than the text; the lexing has all been done for you. Dec 10, 2019 at 0:19
  • 1
    but I don't have the tokens, @another-dave … I wouldn't have this issue if I did
    – scruss
    Dec 10, 2019 at 2:31

3 Answers 3

7

Here's something that should do the trick.

It's written in AWK. Any language I picked will be the wrong language. awk (or gawk) is everywhere.

Copy and paste it into a file (say, expand.awk).

awk -f expand.awk crunched.bas > uncrunched.bas

The script:

# License: Creative Commons 0 https://creativecommons.org/share-your-work/public-domain/cc0/
# Assumes all uppercase, and inability to embed quotes in quoted strings.
# Basic philosophy is to break out the quoted strings, and then process
# the individual statements separated by colons. Then put it all back
# together again.
BEGIN {
    # list of keywords, EXCEPT for 'TO', it's handled special
    keywords="OPEN:CLOSE:CLEAR:INPUT:IF:THEN:ELSE:FOR:NEXT:GOTO:GOSUB:LINE:READ:DEF:DIM:PRINT";
    split(keywords, keylist, ":");
}

{
    # split out the quoted strings. They will be even fields when done.
    split($0, a, "\"");
    for(i = 1; i <= length(a); i++) {
        if (i % 2 == 0) {
            # quoted string, skip it
            continue;
        }
        # expand this chunk of the line
        a[i] = expand_line(a[i]);
    }

    # rebuild the line
    line = "";
    for(i = 1; i <= length(a); i++) {
        p = a[i]
        if (i % 2 == 0) {
            # quoted string, add the quotes back
            p = "\"" p "\"";
        }
        line = line p;
    }
    # clean up PRINT" and INPUT"
    # if these happen to be in quoted strings, kind of out of luck.
    gsub("PRINT\"", "PRINT \"", line);
    gsub("INPUT\"", "INPUT \"", line);

    # out it goes
    print line;
}

function expand_line(line, i, k, l) {
    # break up in to statements
    split(line, statements, ":");
    for(i = 1; i <= length(statements); i++) {
        # expand the keywords for each statement
        for(k = 1; k <= length(keylist); k++) {
            statements[i] = expand(statements[i], keylist[k]);
        }
        # because of GOTO, TO (of FOR TO NEXT) is handled special
        statements[i] = expand_to(statements[i]);
    }
    # rejoin with the :
    line = statements[1];
    l = length(statements);
    if (l == 1) {
        return line;
    }
    for(i = 2; i <= l; i++) {
        line = line ":" statements[i];
    }
    return line;
}


function expand(line, keyword) {
    # for each key word, check for space at each end
    rx=keyword "[^ ]";
    if (match(line, rx) > 0) {
        gsub(keyword, keyword " ", line);
    }
    rx="[^ ]" keyword;
    if (match(line, rx) > 0) {
        gsub(keyword, " " keyword, line);
    }

    return line;
}

function expand_to(line) {
    # hide any GOTOs
    gsub("GOTO", "xxx", line);

    # fix the TO
    if(match(line, "TO[^ ]") > 0) {
        gsub("TO", "TO ", line);
    }
    if(match(line, "[^ ]TO") > 0) {
        gsub("TO", " TO", line);
    }

    # put the GOTOs back
    gsub("xxx", "GOTO", line);
    return line;
}
1
  • Thanks for that. Good parser. It really has trouble with RESTORE, unsurprisingly
    – scruss
    Dec 15, 2019 at 16:07
6

If you're happy with a .net core solution you could take my MS Basic clone, load the code and then save it again.

https://github.com/peteri/ClassicBasic

You would need to remove the EditLine function in the GlassTeletype which uses Win32 interop to provide an line editor (invoked by edit nnnn where nnnn is the linenumber) and maybe give it a little tweak to get rid of the BOM when it saves.

6
  • I tried to build this under Mono the other week with no success. I don't see any way that this will work for me, sorry
    – scruss
    Dec 10, 2019 at 2:29
  • Ah that's possibly because it's a netcoreapp 2.0. If I get a chance I'll give it a try at running on Linux over the weekend because there's very little OS specific in there. Do you actually need mono or would dotnet core be okay?
    – PeterI
    Dec 10, 2019 at 9:43
  • 1
    Okay I'll setup dotnet core on Linux at home (I don't think my home PC is currently setup) this evening and get back to you.
    – PeterI
    Dec 11, 2019 at 13:50
  • 1
    Added instructions for ubuntu and dotnet core 3.1. in readme.md on this branch github.com/peteri/ClassicBasic/tree/UpgradeToCore3.1 however I've only tested in a WSL container.
    – PeterI
    Dec 12, 2019 at 1:01
  • 1
    thank you. That built fine. And it does add spaces, too. Nice collection of games that come with it, too
    – scruss
    Dec 15, 2019 at 15:56
1

Applesoft BASIC appears to not care about whitespace, but it gets a little weird with AT/ATN/TO. For example, you can write A TO X AT O, but if you crunch the whitespace out it will instead be parsed as A TO XA TO. For everything else, whitespace is ignored. ON ERR GO TO becomes ONERR GOTO. The trick when scanning is to find the longest match (ON is also a valid keyword).

I don't know how various dialects of BASIC compare in this respect. Just be aware that not everything that appears to ignore whitespace actually does.

I've written code that converts a text file with an Applesoft BASIC program to tokenized form, as well as code that converts the tokenized program to text. They're built into CiderPress. You can find the C++ source for the parser here, and the pretty-printer here. If your sources were in Applesoft you could launder them quite easily. For anything else some coding will be required.

(There's a comment in the parsing code with a chunk of the 6502 code from Applesoft, so you can see how it actually parses AT/ATN/TO.)

2
  • They're not in Applesoft, but thanks. CiderPress is a great tool and deals with Applesoft BASIC's quirk rather well.
    – scruss
    Dec 11, 2019 at 14:36
  • @scruss: I figured it might work if the code in question used a subset of Applesoft, e.g. your trivial example worked fine. I don't know enough about the BASIC you're targeting to tell where it'll break down, or how much work would be required to adapt the existing code (e.g. swapping out elements of the token table could be straightforward).
    – fadden
    Dec 11, 2019 at 17:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .