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 »

Yep, it's your code, with a couple of syntax changes.

Neil
User avatar
BB8
Posts: 57
Joined: 01 Nov 2020
Location: Tatooine

Re: Neolithic Tiny Basic

Post by BB8 »

In the compare sub, I think I could spare you some bytes (UNTESTED!!).

- libsub16 returns with the result in A/Y and an evaluation in Carry: C=1 is GTE, C=0 is LT
- [A OR Y] gives another evaluation in Z: Z=1 is equal, Z=0 is not equal

Code: Select all

[...]
cmp_2:
;   GetChar();
	jsr getchar
;   SkipWhite();
	jsr skipwhite
;   param2 = Expression();
	jsr expression
    ; p1 is on the stack
    ; p2 is in y:a
	jsr libsub16			; A/Y = p1-p2, while C says if p1 >= p2

	plx
	plx
	sty maths1+1
	ora maths1+1			; at this point C tells if GTE/LT, and Z tells if EQ/NEQ

	; let's have in .A: 1=LT, 2=EQ, 4=GT
	bne cmp_gtlt			; if not equal then consider if GT or LT
	lda #2				; is EQ then value=2
	bra cmp_done
cmp_gtlt: 
	lda #1				; for LT value=1
	bcc cmp_done			; if LT then done
	lda #4				; is GT then value=4
cmp_done: 
	ldy cmp_comp			; retrieve the comparison operator
	and cmp_tab-EQUAL,y    	; compare with the operator mask table
	bne cmp_yes			; if any bit is on then the comparison succeeded
	;beq cmp_no			; no bit on then the comparison failed

cmp_no:
	clc
	bra cmp_99
cmp_yes:
	sec
cmp_99:
	plx
	rts

cmp_tab:  db 2,5,6,4,3,1 		; bit#2 = GT, bit#1 = EQ, bit#0 = LT;    op: =,!=,>=,>,<=,<
; EQ = 2
; NEQ = GT or LT = 4+1 = 5
; GTE = GT or EQ = 4+2 = 6
; GT = 4
; LTE = EQ or LT = 2+1 = 3
; LT = 1
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Thanks BB - I'll check it later. In the meantime, I've added a couple of features:
  • ranges in listing, e.g. list, list 50, list 50-100
  • a break key (esc) which is tested after each iteration
  • line number associated with a (primitive) error message
and it's still sixteen bytes short of 4k!

I'll post the code tomorrow when I've cleaned up the most recent cruft, then we can look at ensmallifying it :mrgreen:

I'll also do a quick write up of the 'features' of this.

Neil
User avatar
BB8
Posts: 57
Joined: 01 Nov 2020
Location: Tatooine

Re: Neolithic Tiny Basic

Post by BB8 »

Maybe it's possible to enable the value of an expression as a boolean. (Untested of course)

Code: Select all

cmp_1:
;   {
;       // we expected a comparator token but didn't see one
;       err = ERR_SYNTAX;

	lda look
	cmp #CR			; if the line isn't at its end
	bne cmp_synterr	; then error

; this part evaluates if the expression is =0 (false) or !=0 (true)
; thus enabling the syntax   IF <expression>   without comparison
	pla
	ply
	sty maths1+1
	ora maths1+1
	beq cmp_no
	bra cmp_yes

cmp_synterr:
	        lda #ERR_SYNTAX
        	sta err
;   }
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Looks like you're doing something similar to what I wanted: given that the comparison operators are all in sequence, it ought to be possible to e.g. come up with a scheme that generates the correct bits to match the operator (or the operator offsets from the first) and eor to give zero if the match is correct. Still needs converting to carry set = comparison true.

It's certainly a _clumsy_ routine at the moment, I think, ripe for improvement (and if I can save a few bytes, I can include peek and poke (probably as @ and ! or some similar one-character keyword to save space in the lookup table).

Here's the current offering. It's intended to run on the Symon emulator, in 65c02 mode, and Multicomp mode, or with a change of ACIA addresses, anything with a 6850 UART and prom at the top of memory. It's built for $e000 simply because that's where Symon expects it to be.

<outdated assembly listing removed; look later in this thread!>

Rough Guide to Neolithic Tiny Basic
Entering a program
The basic program is entered with one statement to a line - so 'for xxx goto' would be two lines. There are no multi-statement lines allowed. Each line is preceded by a line number; these may be entered in any order and will be automatically sorted. Consider incrementing the number count by ten or so to leave space for later additions.
Any line which begins with a number (leading whitespace is ignored) is treated as a program line and entered into memory. However, there are a number of instructions which can be entered the console and which are immediately executed. Some instructions are valid both immediately and in a program.
Variables
There are twenty-six 16-bit signed integer variables - a to z - which may be accessed in either upper or lower case. There is also a single string variable $ to which a string variable may be applied; individual members of the string can also be accessed as well as the complete string.
Instructions
Instructions may be entered in any case, but are internally converted to lower case for display. Text within quotation marks is not altered. With the exception of any leading spaces (between a line number and the instruction) no change is made to the text beyond tokenising the keywords (which is invisible to the user but saves memory space).
  • let - immediate, program - assign a value of an expression to a variable or to the string. An expression may be any sequence of numbers and variables, with arithmetic operators applied in the normal priorities unless overridden by brackets (). Note that the 'let' keyword is optional; e.g.
    let q = 7
    a = -2
    p = (2*2*2)*4*355/(3*113)
    let $ = "hello"
    $ = "world"
  • ' - program - a comment line for programmer convenience
  • print - immediate, program - print either a quoted string, the string variable, a character within the string variable, a variable, or the value of an expression. An integer variable may be preceded by '%' to give a right-formatted output and all integers are followed by a space; strings are not. Any print command produces a line feed after it prints unless it is followed by a semicolon. Zero or more parameters may be printed in a single statement. e.g.
    print
    print "hello ";"world" (printed on the same line; note the space after "hello")
    print (2*2*2)*4*355/(3*113)
    print hello (prints the values of the variables h, e, l (twice), and o, on separate lines)
    print $
    print $[4] (the fifth character of the string)
  • list - immediate - list either all the program, a single line, or a range of lines. An attempt is made to indent the code according to the loop structures used; in general, if the indentation looks 'right' you probably didn't forget anything. e.g.
    list
    list 100
    list 50-150
  • new - immediate - clear the program memory
  • run - immediate - execute the program memory from the lowest-numbered line
  • input - program - assign a value from the keyboard to a variable or a string. Note that any required cue must be supplied by a previous 'print' instruction. e.g.
    input a (accept a numeric value)
    input $ (accept a string)
  • if - program - test the value of the following expression and if it is true, execute code starting with the next line. If false, go directly to the line following the matching endif; that endif is required. If clauses can be nested (each on their own line). e.g.
    ... if a < 7
    ... print a
    ... endif
  • else - program - an optional marker: if present, and the 'if' clause evaluates as false, then code executes from the line following the else statement.
  • endif - program - required terminator for an if clause; each 'if' requires a matching 'endif'. See above for example.
  • for - program - a loop mechanism which is always iterated at least once. A variable is assigned to the 'for' value and the following lines are executed until that variable is equal to the 'to' variable plus one. The variable is tested at the beginning of the loop. The variable is incremented by one automatically but may be altered (carefully) within the loop. If this variable is altered in such a way that it does not end with a value that matches 'to' plus one, the loop will never end.
    Execution continues until the necessary 'next' statement (below) at which point the loop repeats. When the loop ends, program flow continues at the line following the 'next'. e.g.
    ... for q = 1 to 10
    ... print q
    ... next
  • to - program - necessary adjunct to 'from'; see above for example
  • next - program - terminating statement of the for/to/next clause. Necessary; note that loops may be nested and each level must have its own 'next'
  • while - program - A loop construct which can execute either no, some, or infinitely many times. The expression following 'while' is evaluated each time around the loop; if it is false then execution continues after the matching 'wend' (below). The loop may be nested; each level requires a matching wend. Note that there is no modification within the while loop of any of the variables unless the programmer adjusts them directly. e.g.
    ... q = 1
    ... while q <= 10
    ... print q
    ... q = q + 1
    ... wend
  • wend - program - necessary terminating statement for the while/wend clause.
  • goto - program - move program execution to a specified line number (which can be calculated as an expression). Considered harmful! Using 'goto' to enter or leave a structured loop will almost certainly lead to tears before bedtime. e.g.
    goto 150
    goto 150+q*10
  • gosub - program - transfer the program execution to the specified line number (again, like goto, the target address may be calculated. Execution continues until a matching return is encountered. It may be nested.
    gosub 1000
    ...
    1000 ' do gosub stuff
    ...
    1100 return
  • return - program - necessary terminator to gosub. See above.
  • end - program - terminate execution and return to the console. Not required if the program flows to the last line, but useful if, say, higher lines contain subroutines and the program should not run into them.
  • ! - program - set a memory location with an eight-bit value. Both the location and the value can be any valid expression e.g.
    ... ! 512, 65 (writes the value 65 ('A') to the first byte of the string variable)
    Beware that you can crash your program quite easily with this instruction...
  • @ - immediate, program - return an eight bit value from a memory location - the opposite to ! above. Note that this is not strictly a statement but instead a modifier to the expression parser, so using it on a line of its own is a syntax error. However, it can be used anywhere an expression is expected, though it may be necessary to wrap it in parentheses to force the evaluation. e.g.
    ... q = @ 513 (sets q to the value of the second byte of the string variable)
    ... print (@ 513) (prints the value of the second byte of the string variable. Requires the parentheses since print doesn't expect '@'; no variable is set.
Comparison operators
These do what you'd expect; the left operand is compared with the right operand and the result is either true or false to if/then or while/wend. The comparisons are all 16-bit signed arithmetic. The operators are:
  • = - are the operands equal?
  • != - are the operands different?
  • < - is the left operand greater than the right operand?
  • <= - is the left operand greater than or equal to the right operand?
  • > - is the left operand smaller than the right operand?
  • >= - is the left operand smaller than or equal to the right operand?
Errors
There are only three errors possible in Neolithic Tiny Basic:
  • Memory! - you exceeded the available space for your basic program while entering text. Remember that white space within a program is ignored and can be safely removed (except within quoted strings); that leading spaces before a statement are removed automatically; and that the indentation you see in a listing is not stored in memory.
  • Div/0 - you tried to divide a variable by zero. Don't.
  • What? - a syntax error has occurred - usually because you spelled an instruction incorrectly or missed a parameter.
    If you try an execute an instruction in immediate mode which is only available in program mode (e.g. an if/endif instruction) then this error will be printed; if the error is in the running program, it will stop, print the line number and then the error, and return to immediate mode.
Memory
The basic program is stored with keywords compressed from $300 (768) upwards.
The string variable is stored from $200 (512) upwards. Strings are terminated with a zero byte; the maximum length which can be entered from an instruction line is about 75 characters. However, the string space can be directly manipulated using ! and @ - though remember that if you want to create a printable string, it must start a $200 and it must terminate with a zero byte.
Zero page is used for local storage by the various instructions, plus the twenty-six integer variables.
Stack space is heavily used, both for return addresses but also parameters passed to subroutines.

API
Most subroutines take one parameter, which is passed in the Y:A registers or (for bytes) in the A register; the result is passed the same way. When a second parameter is required - for example, in the arithmetic routines and a couple of other places - then the first parameter is pushed to the stack and the second is passed in Y:A. There are (at present) no routines requiring more than two parameters.
Local variables are generally stored in zero page, if they are not required to allow nested (recursive) operation. If they are, they are stored on the stack. The X register is used as an offset into the stack, so must be preserved by all calls. An example of the stack after a call with two parameters and with local variables might be:

Code: Select all

two bytes -->[calling parameter]
two bytes -->[return address]
one byte  -->[previous stack frame pointer - original X]
two bytes -->[local param 1]
two bytes -->[local param 2]
one byte  -->[temporary storage]
one byte  -->[temporary storage]
...
The calling parameter and the local parameters are all addressed using ABS,X instructions, where X is set after pushing its previous value. Temporary storage is accessed using the usual stack operators.

Neil

Edit 10/1/25 to update new instructions, document memory use, and explain the stack convention/ABI.
Edit 26/3/25 to include if/else/endif description.
Last edited by barnacle on Wed Mar 26, 2025 3:35 pm, edited 2 times in total.
User avatar
Mr SQL
Posts: 37
Joined: 14 Aug 2015
Location: ENCOM mainframe.
Contact:

Re: Neolithic Tiny Basic

Post by Mr SQL »

barnacle wrote:
Looks like you're doing something similar to what I wanted: given that the comparison operators are all in sequence, it ought to be possible to e.g. come up with a scheme that generates the correct bits to match the operator (or the operator offsets from the first) and eor to give zero if the match is correct. Still needs converting to carry set = comparison true.

It's certainly a _clumsy_ routine at the moment, I think, ripe for improvement (and if I can save a few bytes, I can include peek and poke (probably as @ and ! or some similar one-character keyword to save space in the lookup table).

Here's the current offering. It's intended to run on the Symon emulator, in 65c02 mode, and Multicomp mode, or with a change of ACIA addresses, anything with a 6850 UART and prom at the top of memory. It's built for $e000 simply because that's where Symon expects it to be.
tiny.asm
Rough Guide to Neolithic Tiny Basic
Entering a program
The basic program is entered with one statement to a line - so 'for xxx goto' would be two lines. There are no multi-statement lines allowed. Each line is preceded by a line number; these may be entered in any order and will be automatically sorted. Consider incrementing the number count by ten or so to leave space for later additions.
I read the thread on Symon, very cool 8-bit VM for running Neolithic Tiny BASIC.
Looks like Neolithic's architecture could be implemented for a classic 8-bit with a VIA and serial port.

Interesting not to allow ":" in Neolithic. There are classic BASIC implementations like TI-BASIC that do not support the concatenator either.

I find it very valuable in BASIC programming because it makes CR/LF a block terminator for if statements.

if x=1 then y=1:z=1

And for if else statements:

if x=1 then y=1:z=1 else y=0:z=0
Load BASIC from tape on your Atari 2600:
http://RelationalFramework.com/vwBASIC.htm
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Looking into BB8's comments on comparisons, with thanks, found a minor bug where input sometimes it got the sign of the input incorrect. I forgot to clear a variable shared with the maths routines.

Here's a diff against the most recently posted source:

Code: Select all

2986a2987
> 		stz sign				; assume it's positive
2992,2993c2993
< 			lda #1
< 			sta sign
---
> 			inc sign
The new code starting at line 2987 should look like

Code: Select all

		stz sign				; assume it's positive
		lda look
		cmp #'-'
		bne inp_1
;		{
;			minus = true;
			inc sign
;			GetChar();		// if we start with a -, note and move past
Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Further bug-fixes:
  • used unsigned test in memmove instead of signed; fixed and made smaller
  • fixed broken signed test in compare
  • re-ordered the comparison tokens to simplify compare
Current size is $f8e, 3982... thinking about peek (@?) and poke (!) to allow for example

Code: Select all

    ! target,byte
    q = @target
    print @target
but still thinking this through...
Neil
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

I thought it through partly...
Screenshot from 2025-03-08 21-44-13.png
Screenshot from 2025-03-08 21-44-13.png (3.06 KiB) Viewed 3415 times
Poke - to save memory, I use ! instead of the longer actual word - behaves as you'd expect. In the example above, it's writing a byte to the string space at $200 (512) and changing the original 'd' to a '2'.
Peek - I'm using @ - allows assignment of an integer at a 16-bit address to a variable. In both cases they're addressing the right byte, but peek in particular is a bit odd and I'm not happy yet.

At the moment, it's a special case in assignment() which is why it can't be used anywhere else. It's not a 'real' keyword (indeed, it doesn't even have a token) so I can't use it for example with print, or as part of a control loop. On reflection I think it needs to be wrapped in expression() which evaluates simple or complex values everywhere else... something to look at I think. But I'm down to under thirty bytes spare...

Neil

edit: it occurs to me that ! allows the creation of self-modifying basic code. Sometimes I worry me...
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Neolithic Tiny Basic

Post by drogon »

barnacle wrote:
I thought it through partly...
Screenshot from 2025-03-08 21-44-13.png
Poke - to save memory, I use ! instead of the longer actual word - behaves as you'd expect. In the example above, it's writing a byte to the string space at $200 (512) and changing the original 'd' to a '2'.
Peek - I'm using @ - allows assignment of an integer at a 16-bit address to a variable. In both cases they're addressing the right byte, but peek in particular is a bit odd and I'm not happy yet.

At the moment, it's a special case in assignment() which is why it can't be used anywhere else. It's not a 'real' keyword (indeed, it doesn't even have a token) so I can't use it for example with print, or as part of a control loop. On reflection I think it needs to be wrapped in expression() which evaluates simple or complex values everywhere else... something to look at I think. But I'm down to under thirty bytes spare...

Neil

edit: it occurs to me that ! allows the creation of self-modifying basic code. Sometimes I worry me...
Er... Hurrah and Hmmm in equal measures...

The indirection code is in the expression evaluator in my TB but there is a sort of special (or odd, depending on your view) to handling things like

Code: Select all

X = RND
and

Code: Select all

RND=X
, so

Code: Select all

A=?B
and

Code: Select all

?B=A
.

I've not tried self modifying code .... yet... But isn't the usual challenge to write a program that when RUN does a LIST (assuming you can't use LIST at run-time)...

And welcome to code golfing ;-) Now it gets fun...

Currently I have 10 bytes spare, but my code contains the bit-banged serial IO too. (as well as save/load)

And to make you feel better, Acorn Atom Basic is 4KB too - however it has a 4KB "operating system" ROM to do the fancy IO stuff...

-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 »

Hmm. It looks like a better place to put the @ is in 'factor'; that way I can assign a variable directly with

Code: Select all

q = @512
and the same way in code. But to use it with print, unless I modify print (and anywhere else that might use @, so any instruction that takes a parameter) I need to wrap it in brackets:

Code: Select all

print (@512)
because the @ isn't recognised as a number, a string, or a variable - but the brace forces an immediate recursive call to expression().

I think I can live with that. So now the length is $fe2 - 30 bytes left? Though I think I may have just found some push and pulls I can rationalise.

Neil
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Neolithic Tiny Basic

Post by barrym95838 »

I've been really busy lately, so I didn't have time to test this, but it's based on Bruce Clark's code, so I know it started out tight and fast. It's still tight and fast, and hopefully correct, if I didn't mess something up adapting it.

Code: Select all

;-----------------------------------------------------------------------
;
; Memmove - move memory overlapping memory safely in either direction
; Uses mm_from, mm_to, mm_size in zero page, and expects them to be
; valid before the move is made

        bss
mm_from         ds 2
mm_to           ds 2
mm_size         ds 2    ; three parameters for memory move

        code
memmove:
        phx
        phy
        ldy mm_from
        lda mm_from+1
        cpy mm_to
        bne memo_nz
        cmp mm_to+1
        beq mov_done    ; if the source and dest is the same
        cpy mm_to
memo_nz:
        sbc mm_to+1
        bvs memo_1
        eor #0x80
memo_1:
        asl a           ; carry set if from => to
        bcc mov_dec
mov_inc:                ; 'from' > 'to' ... the smallest addresses
        ldy #0          ;   must be copied first to prevent data
        ldx mm_size+1   ;   corruption if the ranges overlap
        beq mov_i3
mov_i2:                 ; copy as many 256-byte chunks as we can
        lda (mm_from),y ;   first using a tight inner loop ...
        sta (mm_to),y
        iny
        bne mov_i2
        inc mm_from+1   ;   update the high pointer bytes
        inc mm_to+1
        dex
        bne mov_i2      ; next 256-byte chunk
mov_i3:
        ldx mm_size     ; ... then copy the remaining size mod 256
        beq mov_done    ;   bytes with a less efficient loop
mov_i4:
        lda (mm_from),y
        sta (mm_to),y
        iny
        dex
        bne mov_i4
        bra mov_done
mov_dec:                ; 'from' < 'to' ... the largest addresses
        ldx mm_size+1   ;   must be copied first to prevent data
        clc             ;   corruption if the ranges overlap
        txa             ;
        adc mm_from+1   ; adjust the addresses of 'from' and 'to'
        sta mm_from+1   ;   by adding 'size' to each
        clc
        txa
        adc mm_to+1
        sta mm_to+1
        inx             ; (saves us from a cpx #$ff at the bottom)
        ldy mm_size     ; copy 'size' mod 256 bytes first ...
        beq mov_d4
        bra mov_d3
mov_d2:
        lda (mm_from),y ;   (inner copy loop)
        sta (mm_to),y
mov_d3:
        dey
        bne mov_d2
        lda (mm_from)   ;   (unroll y=0 to keep inner loop tight)
        sta (mm_to)
mov_d4:
        dey
        dec mm_from+1   ;   update the high pointer bytes
        dec mm_to+1
        dex             ; ... then copy all the remaining 256-byte
        bne mov_d2      ;   chunks
mov_done:
        ply
        plx
        rts
This is significantly smaller and faster than your memmove code that it's intended to replace, but I apologize for dropping it on you without any proper testing.
[It looks like the forum software converted my tabs to spaces ;-(]
Last edited by barrym95838 on Sun Mar 09, 2025 10:31 pm, edited 1 time in total.
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Neolithic Tiny Basic

Post by barnacle »

Mike, I'll check your version sometime in the week, thanks. Smaller is better because it leaves space for other stuff :) Faster is helpful but not critical since it doesn't get used in execution, just in entering code (though adding one line at the bottom with 30k of text already entered might not be the snappiest response...)

(As far as I can tell, the forum code block replaces tabs with three spaces...)

Anyway, here's my latest version, hopefully with fewer bugs...
  • Uses ! for poke and @ for peek as described above.
  • Cleans up the error reporting a little; if an error occurs, its line number is printed before the error (most commonly 'what?' for a syntax error) unless in immediate mode, in which case no line number is printed.
  • Saved a byte or two in term() by removing redundant stack storage.
  • Saved a byte or six in factor() by changing behaviour on div/zero: previously, it replaced the operand with zero before returning but now it doesn't.
  • No longer produces a syntax error on an empty immediate line
  • No longer prints an unnecessary crlf before an error report
  • Prints error line number left justified instead of right
Neil
tiny.asm
(75.55 KiB) Downloaded 166 times
User avatar
BigDumbDinosaur
Posts: 9426
Joined: 28 May 2009
Location: Midwestern USA (JB Pritzker’s dystopia)
Contact:

Re: Neolithic Tiny Basic

Post by BigDumbDinosaur »

barrym95838 wrote:
[It looks like the forum software converted my tabs to spaces ;-(]

Tab rendering is not part of any HTML standard, and in the case of this forum, a PHP interpreter is behind the curtain and pulling the levers that result in HTML being emitted.

When HTML is emitted by running a PHP script as part of rendering a web page, a tab *might* be converted into multiple consecutive ASCII blanks ($20).  Web browsers’ rendering engines will emit a single blank in place of consecutive ASCII blanks.  If multiple consecutive blanks are wanted, a series of   HTML entities has to be substituted.

Another method, which I use on pages that don’t process user-supplied HTML, as is the case here, is to insert a “hard” space, which is $A0.  In Billyware, a hard space can be generated by holding down [Alt], typing 160 on the numeric keypad, followed by releasing [Alt].  However, there is no standard for that as well, so it might not work in every case.

As for how the forum software processes a tab, from what I’ve determined in my frequent forays into PHP, there seems to be nothing in that language that does anything special with one.  It apparently gets passed through and when the browser gets it, it may or may not result in multiple blanks being displayed.

ALT-Codes.pdf
Windows ALT Character Keystroke Codes
(132.74 KiB) Downloaded 201 times
x86?  We ain't got no x86.  We don't NEED no stinking x86!
User avatar
BB8
Posts: 57
Joined: 01 Nov 2020
Location: Tatooine

Re: Neolithic Tiny Basic

Post by BB8 »

anyway the <pre> - </pre> tags have existed since the very early versions of Html: they are meant for preformatted monospaced texts, especially valid for displaying source code; with those tags any spaces or tabs are rendered as they were entered.
Attachments
PreExample.png
Post Reply