Page 6 of 15

Re: Neolithic Tiny Basic

Posted: Sun Feb 23, 2025 9:12 pm
by barnacle
barnacle wrote:
  • print with no parameters should just print an empty line; it doesn't
Well that was the easy fix... now it does.

Neil

Re: Neolithic Tiny Basic

Posted: Sun Feb 23, 2025 9:25 pm
by barnacle
And this was easier than I thought, too... it helps no end if you include the test in the code, and not just in the comments :mrgreen: It was actually a bug in the original C version which I had noted but not tested... d'oh.
Screenshot from 2025-02-23 22-23-38.png
Neil

Re: Neolithic Tiny Basic

Posted: Mon Feb 24, 2025 6:48 am
by barrym95838
So, LIST has automatic indentation? Unexpectedly fancy for a "tiny" BASIC ...

Re: Neolithic Tiny Basic

Posted: Mon Feb 24, 2025 7:04 am
by BigDumbDinosaur
barrym95838 wrote:
So, LIST has automatic indentation? Unexpectedly fancy for a "tiny" BASIC ...

Yeah, that is a luxury feature, even for a “big” BASIC.

I still do some development work in Thoroughbred Dictionary-IV BASIC, which is a high-powered rendition that costs several thousand bucks for an eight-user version.  A raw LISTing of a loaded program produces a screen jumble with no formatting, other than pagination.  The program has to be loaded into the full-screen editor (itself written in Thoroughbred BASIC) to get fancy formatting.  So it would seem Neil is one step ahead of the folks at Thoroughbred.  :D

Re: Neolithic Tiny Basic

Posted: Mon Feb 24, 2025 7:40 am
by barnacle
Still under 4k, too :)

The indentation is automatic but pretty dumb; it only applies to if/then, for/next, and (eventually) do/while and it doesn't for example track gotos and gosubs so it's more of a hint than anything else. But it was dead easy to do, and it provides a quick 'if the indentation doesn't look right, the program probably isn't' visibility.

And I recycle the indent variable as the depth counter for recursive routines (as in the two nested for-next loops).

Code: Select all

list_6:
		;if ((ch == IF) || (ch == FOR) || (ch == DO))
		cpx #IF
		beq list_7
		cpx #FOR
		beq list_7
		cpx #DO
		bne list_8
list_7:		;indent++;
			inc indent
list_8:		
		;if ((ch == ENDIF) || (ch == NEXT) || (ch == WHILE))
		cpx #ENDIF
		beq list_9
		cpx #NEXT
		beq list_9
		cpx #WHILE
		bne list_10
list_9:
			;indent--;
			dec indent
			bpl list_10
				stz indent		; don't go negative!
Plus a bit upstream of that that print a couple of spaces for each indent level.

Neil

Re: Neolithic Tiny Basic

Posted: Mon Feb 24, 2025 6:07 pm
by BigDumbDinosaur
barnacle wrote:
The indentation...provides a quick 'if the indentation doesn't look right, the program probably isn't' visibility.

Sounds like Python.  :D

Re: Neolithic Tiny Basic

Posted: Mon Feb 24, 2025 7:27 pm
by BigEd
Nothing like python - whitespace isn't significant in Basic. (BBC Basic also offers automatic indentation, although it's optional and I don't use it myself.)

Re: Neolithic Tiny Basic

Posted: Mon Feb 24, 2025 8:24 pm
by barnacle
Yeah, whitespace here is immaterial, leave it out if you want to save space. I want to be legible, so (a) the line numbers are all right-aligned, and (b) any number of spaces, including none, are removed when the line is tokenised and a single space inserted after the line number in listing. The indentation is then added - it's all done at list time, not stored.

A line's internal storage is: two bytes of line number, one byte of line, the text, tokenised anywhere it isn't quoted, and a CR at the end (which might turn into a /0 at some stage, as then I can do away with having separate routines strlen() and str_to_CR() with minor changes in the text input. But that's a last stage thing.) Tokens are a single 8-bit value above 0x80 which simplifies an awful lot of the decoding, as well as speeding it up.

As it happens, case is immaterial here too; enter in upper or lower case, but tokens are always shown in lower case. Again, that's something that can come out in future if I need to save a byte or three. It's still twice the size of Palo Alto Tiny Basic (though the 8080 tends to be a more dense machine code).

Neil

Re: Neolithic Tiny Basic

Posted: Tue Feb 25, 2025 8:59 am
by barnacle
Screenshot from 2025-02-25 09-54-28.png
IF/ENDIF is now working, with the six comparison operators =, !=, >=, >, <=, and < all apparently working (limited checks but including negative/positive combinations). The comparison code is a bit clunky; there may be a better way. But it's parsing the complete expression (everything after the 'if') and then evaluating it.

3695 bytes so far...

Neil

Re: Neolithic Tiny Basic

Posted: Thu Feb 27, 2025 7:53 am
by barnacle
Do/while is now working... recursively.
Screenshot from 2025-02-27 08-47-11.png
It was a bit tricky since both DO and WHILE have to work properly for anything useful to happen. DO needs to maintain some local variables: the address where the loop starts and the address of the line after the while, the continuation address.

WHILE evaluates the expression, and if it's true, it returns zero/NULL as the next line location; if it's false it returns the address of the following line (here, 60). DO has two internal loops: one which executes a line at a time as long as the line returns neither NULL nor matches the continuation address, and an outer loop which runs the inner loop as long as NULL is returned and there is no error.

So DO executes lines until it executes WHILE; if WHILE returns zero it carries on but otherwise quits and advances to the line after the WHILE.

Also I tidied up the macros that generate the ABS,X variants so they output a single line in the listing and made that a couple of hundred lines shorter... current program size is $f2f aka 3877 bytes. I think I found a place to chew a few bytes out, though, in execute.

Neil

Re: Neolithic Tiny Basic

Posted: Thu Feb 27, 2025 3:25 pm
by teamtempest
I wonder about that implementation of DO/WHILE. There's more than one way to implement such a construct, if what I'm imagining is actually what you're trying to do.

What I'm thinking of is something like this:

StartLine# DO

...

line#s BASIC lines

...

EndLine# WHILE <expr>

What happens is that if <expr> is TRUE, control goes back to StartLine#, and if it is FALSE, control falls through to whatever lines comes after EndLine#.

All DO really has to accomplish when it is executed is to record where it is. One way to do that is to just push StartLine# onto a stack along with a marker that indicates what it is to cover up that line number. When WHILE executes, it pulls the marker at the top of the stack and checks that it is the DO marker (if not, something is mismatched somewhere, and so we have an error). Assuming it is the DO marker expected, pull StartLine# and evaluate <expr>. If the result is TRUE, StartLine# becomes the next line to execute, which will execute the DO again and start the whole cycle over. If FALSE, just fall through to the next line as would normally happen, and let the recorded StartLine# go off to the Big Bit Bucket in the sky.

It would be possible to be more elaborate. DO could also "look ahead" to see if a matching WHILE was actually present. But that's more complicated, and any errors found that way are going to be found anyway during execution as it stands. In a compiler, looking ahead to catch such an error would probably be worth doing. In an interpreter, not so much.

And yes, it would also be possible to use GOTO to jump out of nested DO/WHILE loops and really confuse the interpreter. But that's also already possible with any number of other BASIC control structures.

Re: Neolithic Tiny Basic

Posted: Thu Feb 27, 2025 3:34 pm
by BigEd
(Sounds somewhat like how BBC Basic's REPEAT...UNTIL works. Note that BBC Basic always runs through the body at least once - this very much simplifies the way it needs to work, and means there's never a need to go looking for an UNTIL, it's enough to encounter one. Similarly FOR...NEXT always executes at least once, in BBC Basic, although not in all Basics. Again, it's easier if it does.)

Re: Neolithic Tiny Basic

Posted: Thu Feb 27, 2025 3:40 pm
by drogon
teamtempest wrote:
I wonder about that implementation of DO/WHILE. There's more than one way to implement such a construct, if what I'm imagining is actually what you're trying to do.

What I'm thinking of is something like this:

StartLine# DO

...

line#s BASIC lines

...

EndLine# WHILE <expr>

What happens is that if <expr> is TRUE, control goes back to StartLine#, and if it is FALSE, control falls through to whatever lines comes after EndLine#.

All DO really has to accomplish when it is executed is to record where it is. One way to do that is to just push StartLine# onto a stack along with a marker that indicates what it is to cover up that line number. When WHILE executes, it pulls the marker at the top of the stack and checks that it is the DO marker (if not, something is mismatched somewhere, and so we have an error). Assuming it is the DO marker expected, pull StartLine# and evaluate <expr>. If the result is TRUE, StartLine# becomes the next line to execute, which will execute the DO again and start the whole cycle over. If FALSE, just fall through to the next line as would normally happen, and let the recorded StartLine# go off to the Big Bit Bucket in the sky.
That's essentially what my TinyBasic does - it has separate stacks for FOR, GOSUB and DO as well as another for arithmetic evaluation (and another for the operation of the Intermediate Language VM thingy but that's behind the scenes)

So upon encountering a DO statement it checks the stack depth and if OK then saves the 'cursor' (pointer into the textual program source code - it's all text here, no tokenisation) and carries on.

Until evaluates the next expression then checks the result for true/false - if false, then it peeks the pointer out of the 'do' stack, loads that into 'cursor' and carries on, otherwise it drops the top of the DO stack and carries on without changing the 'cursor'.

"continue" can be effected with an UNTIL FALSE at any time and "break" (actually Do Pop) can be sort of effected with UNTIL TRUE:GOTO ... A true 'break' would need to scan forward to find a corresponding UNTIL statement - and I don't have the code space for that...
Quote:
It would be possible to be more elaborate. DO could also "look ahead" to see if a matching WHILE was actually present. But that's more complicated, and any errors found that way are going to be found anyway during execution as it stands. In a compiler, looking ahead to catch such an error would probably be worth doing. In an interpreter, not so much.
That's when an auto-indenting LIST can be a boon - I do that in my "big" Basic (written in C, runs under Linux), but there just isn't the space for it in my 4K TinyBasic...

-Gordon

Re: Neolithic Tiny Basic

Posted: Thu Feb 27, 2025 3:47 pm
by drogon
BigEd wrote:
(Sounds somewhat like how BBC Basic's REPEAT...UNTIL works. Note that BBC Basic always runs through the body at least once - this very much simplifies the way it needs to work, and means there's never a need to go looking for an UNTIL, it's enough to encounter one. Similarly FOR...NEXT always executes at least once, in BBC Basic, although not in all Basics. Again, it's easier if it does.)
Saving that lookahead time and cycles, but when you have space and cpu cycles then anything's possible :)

My "big" Basic has every form of loop+test, so test at the top with WHILE or UNTIL (opposite polarity tests) or test at the bottom with the same WHILE or UNTIL. The actual loop keywords at REPEAT and CYCLE.

Code: Select all

WHILE foo < 10 CYCLE
  PROC table (foo)
REPEAT
or

Code: Select all

CYCLE
  PROC table (foo)
REPEAT UNTIL foo < 10
and so on. Because why not.

So it's easy to just add and add and add when you have the space a cpu cycles to do so. Personally I think even DO/UNTIL (or REPEAT/UNTIL) are a luxury in Basic - and I was surprised to find that it was "established" in some of those TinyBasics of the 1970s...

-Gordon

Re: Neolithic Tiny Basic

Posted: Thu Feb 27, 2025 4:09 pm
by barnacle
My version tests at the end, but it needs to cope with nested commands (and therefore recursion) so there's a routine which I tell what to look for, and it returns address of the matching command. The same routine works for if/endif, for/next, and do/while. The difference between for/next and do/while are (a) that do/while always executes at least once, while for/next may execute zero times, and (b) there's an automatically incrementing variable in for/next. But all three can be nested within themselves and within each other.

The nice thing about the matching routine is that I don't have to e.g. say 'next a'; a simple 'next' does the job.

It isn't needed for nested gosubs because the flow there is always linear (unless you've done something weird with goto, for which you only have yourself to blame).

The original C code for the two routines:

Code: Select all

char * do_do (char * where)
{
	/* Execute the 'do' clause by calling it recursively; it continues
	 * until the matching 'while' statement. That statement returns NULL
	 * if its comparison clause is true, indicating that the loop should
	 * continue, or the address of the line following 'while'
	 */
	
	char * loop_address;	// first line in the do loop
	char * cont_address;	// first line after the while
	char * while_address;	// the while line 
	
	where -= 4;
	where = find_next_line (where);
	loop_address = where;
	while_address = find_pair (where, DO);
	cont_address = find_next_line (while_address);	// first line after while
	do
	{
		where = loop_address;
		do
		{
			where = execute(where);
		}	
		while ((NULL != where) && (where != cont_address));
	}
	while ((NULL == where) && (ERR_NONE == err));
	return where;
}

char * do_while (char * where)
{
	/* test a condition; if true, return NULL 
	 * otherwise, return the address of the next line
	 */
	if (compare())
	{
		return NULL;
	}
	else
	{
		return find_next_line (where - 4);
	}
}
(The 'where - 4' bits are because handling a line returns the address of the next line to execute; the stored lines have the fourth character as the first instruction (always, and always a single byte) and by the time it knows what to execute it's already pointing at that bit. I might do some more thinking on that.)

Neil