Neolithic Tiny Basic

Programming the 6502 microprocessor and its relatives in assembly and other languages.
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Right, that's improved for/next in a number of ways:
  • renamed some variables for clarity
  • removed an unneeded variable, which reduces stack use
  • changed the comparison to an equality, which solves the odd issues I was seeing when the index crossed zero
  • autoincrement the 'to' value, which allows the equality above to work correctly: 'for q = 10 to 10' runs once, as expected; 'for q = 1 to 10' runs ten times with q ranging from 1 to 10, as expected.
Just replace the for: routine with this:

Code: Select all

for:

to_val		set 1		; local variables on stack
name		set 2		; only one byte but allocate two
next_where	set 3	
where		set 4

;	int16_t to_val;		// to
;	char name;			// and which variable is the counter?
;	char * next_where;	// the address of that line
	phx
	tsx
	phx
	phx					; to_val
	phx
	phx					; name
	phx
	phx					; next_where	
;	// as always, we start pointing at the do token, so need to go back
;	where -= 4;
	sec
	sbc #4
	bcs for_01
	dey					; where = where - 4
for_01:
	phy
	pha					
;	where = find_next_line(where);
;	next_where = where;
	jsr find_next_line
	STAZXLO where
	STAZXLO next_where
	tya
	STAZXHI where
	STAZXHI next_where	
;	GetChar();
	jsr getchar
;	SkipWhite();
	jsr skipwhite
;	if (!isalpha (Look))
	lda look
	jsr isalpha
	bcs for_1
;	{
;		// not a variable?
;		where = NULL;
		lda #0
		STAZXLO where
		STAZXHI where
;		//Expected("variable");
;		err = ERR_SYNTAX;
		lda #ERR_SYNTAX
		sta err
		jmp for_99
;	}
;	else
for_1:
;	{
;		// one of the 26 signed integer variables
;		name = GetName ();
		jsr getname
;		name = name - 'A';
		sec
		sbc #'A'
		asl a					; a <- 0, b <- 2, etc
		STAZXLO name			; save index to var
;		SkipWhite();
		jsr skipwhite
;		Match (EQUAL);
		lda #EQUAL
		jsr match
;		vars[name] = Expression();	// the start value
		jsr expression
		phy
		LDYZXLO name			; the name index
		sta vars,y
		pla
		sta vars+1,y
;		SkipWhite();
		jsr skipwhite
;		Match (TO);
		lda #TO
		jsr match
;		to_val = Expression() + 1;	// the end value
		jsr expression
		clc
		adc #1
		bcc for_11
		iny
for_11:
		STAZXLO to_val
		tya
		STAZXHI to_val	
for_20:
;		do
;		{
;			// recursively execute lines until 'next'
;			while (NULL != where)
			LDAZXLO where
			ORAZXHI where
			beq for_22			; finished if zero
;			{
for_21:
;				where = execute(where);
				pla
				ply
				jsr execute
				phy
				pha
;				if (ERR_NONE != err)
				lda err
;					break;
					bne for_30
;			}	
			bra for_20
for_22:
;			where = next_where;
			LDAZXLO next_where
			STAZXLO where
			LDAZXHI next_where
			STAZXHI where
;			vars[name]++;			// normal step value
			LDYZXLO name
			clc
			lda vars,y
			adc #1
			sta vars,y
			lda vars+1,y
			adc #0
			sta vars+1,y
;		}
;		while ((vars[name] != to_val) && (ERR_NONE == err));
		lda err
		bne for_30				; quit if there was an error
		lda vars,y
		CMPZXLO to_val
		bne for_20				; low byte not equal, do more
		lda vars+1,y
		CMPZXHI to_val			; else test high byte
		bne for_20	
for_30:
;		where = find_pair (next_where, FOR);
		lda #FOR
		sta fmt_pair
		lda #NEXT
		sta fmt_match
		pla
		ply
		jsr find_matching_token
		; where = find_next_line(where);
		jsr find_next_line	
		phy
		pha
;	}
for_99:
;	return where;
	pla
	ply			; get where
	plx
	plx			; discard next_where
	plx
	plx			; discard for_name
	plx
	plx			; discard to_val		
	plx			; restore caller stack frame
	rts;}
Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Here we are again...

Noting that for/next always loops at least once, and that do/while did the same, I decided to replace the latter with while/wend (four bytes shorter than endwhile :) though it's ENDWHILE internally). While/wend loops zero or more times.

For/next runs until the autoincremented variable is equal to the 'to' value, plus one. While loops until the comparison expression is true, and doesn't need an incrementing variable (as my nested example) but could, for example, wait for a value to be set externally - say by an input-returned value, or from a component register (if there were a peek instruction :mrgreen: )

Total length is currently $f8e, 3982 bytes.

A couple more changes I want to make then I'll post it up again.

Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

I'm also seriously considering getting rid of goto...

Neil
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Neolithic Tiny Basic

Post by drogon »

barnacle wrote:
I'm also seriously considering getting rid of goto...

Neil
Once upon a time I demonstrated my "big" Basic ("big" as it's written in C) and as we started I suggested that using the built-in editor (or an external editor) it was possible to write programs not only without line numbers but also without GOTO.

Several members of the audience were aghast and one walked out.

Do you have any other form on ON x GOTO ... or SWITCH/CASE type of construct? My TinyBasic allows for GOTO X where X is an expression - e.g. L*10 and so on. (Same for GOSUB). It's crude, but it is BASIC ...

-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Yeah, that's the problem: when does it stop being Basic? On the other hand, it's _my_ interpretation, and certainly the original Tiny Basics from the seventies were not terribly standardised. As far as I know, everything can be done with a single structure: while. If and For make things easier, but I'm firmly with Dijkstra on goto considered harmful. In spite of the fact that (obviously) assembly us full of 'em... most of my bugs have been issues where I have not jumped to the correct location at the end of a structure.

I have computed goto and gosub, so the same as yours; no switch, no on_x_goto.

I was considering extending this to a no-line-number version, but the biggest issue there is actually the source editor; I think that needs some serious thought as that would have to be either in the basic, or as part of the 65c02 operating system (which doesn't exist in any form yet). For curiosity, how would you define a label?

Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Screenshot from 2025-03-04 12-46-35.png
Oops, I forgot to show the while/end demo.

Neil
teamtempest
Posts: 443
Joined: 08 Nov 2009
Location: Minnesota
Contact:

Re: Neolithic Tiny Basic

Post by teamtempest »

Quote:
For curiosity, how would you define a label?
My first thought would be as a keyword followed by a name. It could be searched for like any other keyword. In the case of a line numbered BASIC (to keep it simple, one statement per line), start from the first line and look at the keyword it starts with. If it's not the label keyword, move to the next line. If it is, check if name is the one being looked for. If not, move to the next line. If the last line is reached without ever finding the name, signal an error. If name is found on some line, transfer control there.

It could be fancier. A pre-run pass could locate all the label keywords and link them together, so at run time only lines with the label keyword would be examined. Each label line could have a pointer to the next label, or the location of each one cold be put into a sorted table which could be searched in log time to find the correct label. Kind of ambitious for a Tiny BASIC, but if it's being done just for fun, why not?
Quote:
For/next runs until the autoincremented variable is equal to the 'to' value, plus one.
Have you considered the case of the final value being less than the start value right from the beginning? For simplicity, assuming the step value is always plus one, something like FOR I = 100 TO 0. Should that execute once or not at all?

I've read that the the original developers at Dartmouth decided something like that should never execute. To me that suggests some kind of look-ahead to find the matching NEXT. Which raises questions of what to do if one FOR loop has more than one NEXT (presumably there's a GO TO somewhere that directs execution to one of two or more paths, each of which ends in a NEXT).
teamtempest
Posts: 443
Joined: 08 Nov 2009
Location: Minnesota
Contact:

Re: Neolithic Tiny Basic

Post by teamtempest »

Here's a little monstrosity that someone evil might try. What should happen? I note that some online BASICs simply refuse to even give it a go:

100 GOTO 225
125 PRINT "SECOND NEXT"
150 NEXT
175 PRINT "DONE"
200 END
225 FOR I = 1 TO 10
250 IF I > 5 GOTO 125
275 PRINT "FIRST NEXT"
300 NEXT
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Screenshot from 2025-03-04 15-05-29.png
Here's a traditional test: on the simulator at a nominal 8MHz it took almost exactly two minutes (and at a nominal 1MHz twelve; something ain't right!)

It did the top two lines but scrolled them off the top.

Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Your evil code sticks in a loop, I don't know what's going on but I refer m'learned colleague to 'goto considered harmful'.

Neil
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Neolithic Tiny Basic

Post by drogon »

barnacle wrote:
I was considering extending this to a no-line-number version, but the biggest issue there is actually the source editor; I think that needs some serious thought as that would have to be either in the basic, or as part of the 65c02 operating system (which doesn't exist in any form yet). For curiosity, how would you define a label?

Neil
My other "language" - Apricot doesn't have a GOTO however like Basic it uses a line editor based on line numbers.

As does COMAL - No GOTO there.

In my other Basic, I have GOTO a line number, but also GOTO a label - and like assembler, labels are a string of letters and numbers terminated with a colon on a line by themselves. Nothing clever like anonymous labels for short in-subroutine jumps, etc.

My 65C02 Operating system doesn't have an editor built-in but there is a nano-like one written in C which loads in from disk. My BCPL OS (portable, '816, RISC-V, ARM) has the same nano-like editor but written in BCPL. I've always wanted a BASIC for that system - maybe one day.

-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Neolithic Tiny Basic

Post by drogon »

barnacle wrote:
Screenshot from 2025-03-04 15-05-29.png
Here's a traditional test: on the simulator at a nominal 8MHz it took almost exactly two minutes (and at a nominal 1MHz twelve; something ain't right!)

It did the top two lines but scrolled them off the top.

Neil
Yay! I recognise that :)

-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Neolithic Tiny Basic

Post by drogon »

teamtempest wrote:
Here's a little monstrosity that someone evil might try. What should happen? I note that some online BASICs simply refuse to even give it a go:

100 GOTO 225
125 PRINT "SECOND NEXT"
150 NEXT
175 PRINT "DONE"
200 END
225 FOR I = 1 TO 10
250 IF I > 5 GOTO 125
275 PRINT "FIRST NEXT"
300 NEXT
In my TB, it does what I think I'd expect it to do:

Code: Select all

>LIST                                                                                    
  100  GOTO 225                                                                          
  125  PRINT "SECOND NEXT"                                                               
  150  NEXT I                                                                            
  175  PRINT "DONE"                                                                      
  200  END                                                                               
  225  FOR I = 1 TO 10                                                                   
  250  IF I > 5 GOTO 125                                                                 
  275  PRINT "FIRST NEXT"                                                                
  300  NEXT I                                                                            
                                                                                         
>RUN                                                                                     
FIRST NEXT                                                                               
FIRST NEXT                                                                               
FIRST NEXT
FIRST NEXT                                                                               
FIRST NEXT
SECOND NEXT
SECOND NEXT
SECOND NEXT
SECOND NEXT
SECOND NEXT
DONE
-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

On retrying and typing it correctly (for my syntax):
Screenshot from 2025-03-04 15-40-51.png
And correctly continues to the next line. I am not at all sure what's happening there, but on NTB, next is just a marker, so I suspect that the next at 150 is just returning a null to the inner loop; the one that matters and has to find the next line so it knows where to go is in the for at 225.

Neil
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Neolithic Tiny Basic

Post by drogon »

barnacle wrote:
Screenshot from 2025-03-04 15-05-29.png
Here's a traditional test: on the simulator at a nominal 8MHz it took almost exactly two minutes (and at a nominal 1MHz twelve; something ain't right!)

It did the top two lines but scrolled them off the top.

Neil
This takes 10.5 minutes on my 2MHz (6507) system. A tokenised BASIC ought to be must faster. (Much!)

Code: Select all

Mandelbrot - Gordons TinyBasic - Integers
Start
......,,,,,,,,,,,,,,,,,,,,,,'''''''''~~~~~=+:*$O:;==~~'''''',,,,,,,,,,,........
.......,,,,,,,,,,,,,,,,,,,,,'''''''''~~~==+%*O  ;;%=~~~~''''',,,,,,,,,.........
........,,,,,,,,,,,,,,,,''''''''~~~'====++:       %++=~~~~'',,,,,,,,,..........
........,,,,,,,,,,,',,,,'''''''~~~=++::+;;*       %;:++===='''',,,,,,..........
.....,,,,,,,,,,,,''''''''~~~~~~~==+; &$%              ;*;$+=~',,,,,,,,,,.......
...,,,,,,,,,,,''''''''''~~~~~~===::;                       +~'''',,,,,,,,,.....
....,,,,,,,,,,,'''~~~=========+++&                       %:==~~''',,,,,,,......
.....,,,,,,''''''~~=+X::+; :+++:;%                        &&=~~''',,,,,,,......
....,,''''''''~~~~=+:%  *O  B*;*%                          $=~~'',,,,,,,,......
,,,,,,''''''~~~~~=++;*X        B                            =~~''',,,,,,,,,,,,.
,,,,,'''~~~~=====;*%&                                      +=~~'',,,,,,,,,,,,,.
,,,,''~~==++++:+*&                                       O:=~~'''''',,,,,,,,,,.
,,,,'                                                   %;+==~~'''',,,,,,,,,,,.
,,,,''~~==++++:+*&                                       O:=~~'''''',,,,,,,,,,.
,,,,,'''~~~~=====;*%&                                      +=~~'',,,,,,,,,,,,,.
,,,,,,''''''~~~~~=++;*X        B                            =~~''',,,,,,,,,,,,.
....,,''''''''~~~~=+:%  *O  B*;*%                          $=~~'',,,,,,,,......
.....,,,,,,''''''~~=+X::+; :+++:;%                        &&=~~''',,,,,,,......
....,,,,,,,,,,,'''~~~=========+++&                       %:==~~''',,,,,,,......
...,,,,,,,,,,,''''''''''~~~~~~===::;                       +~'''',,,,,,,,,.....
.....,,,,,,,,,,,,''''''''~~~~~~~==+; &$%              ;*;$+=~',,,,,,,,,,.......
........,,,,,,,,,,,',,,,'''''''~~~=++::+;;*       %;:++===='''',,,,,,..........
........,,,,,,,,,,,,,,,,''''''''~~~'====++:       %++=~~~~'',,,,,,,,,..........
.......,,,,,,,,,,,,,,,,,,,,,'''''''''~~~==+%*O  ;;%=~~~~''''',,,,,,,,,.........
......,,,,,,,,,,,,,,,,,,,,,,'''''''''~~~~~=+:*$O:;==~~'''''',,,,,,,,,,,........
Finished/[code]

[code]    0 REM Mandelbrot
  100 VDU12:PRINT "Mandelbrot - Gordons TinyBasic - Integers"
  110 PRINT "Start" : VDU 7
  120 REM !160=0:REM Initialise TIME in Ruby board
  130 Z=TOP:$Z=".,'~=+:;*%&$OXB#@ "
  140 F=50
  150 FOR Y = -12 TO 12
  160 FOR X = -49 TO 29
  170 C=X*229/100
  180 D=Y*416/100
  190 A=C:B=D:I=0
  200 Q=B/F:S=B-(Q*F)
  210 T=((A*A)-(B*B))/F+C
  220 B=2*((A*Q)+(A*S/F))+D
  230 A=T: P=A/F:Q=B/F
  240 IF ((P*P)+(Q*Q))>=5 GOTO 280
  250 I=I+1:IF I<16 GOTO 200
  260 PRINT" ";
  270 GOTO 290
  280 VDU ?(Z+I)
  290 NEXT X
  300 PRINT ""
  310 NEXT Y
  320 Q=!160
  330 PRINT"Finished" : VDU 7
  340 REM PRINT"Time: ", Q/100, " secs."
-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
Post Reply