Dandelion BASIC devlog
We’re up off the ground and flying… sort of.
Let me explain a bit of my thinking process.
Much of the difficulty over the past few days has been taking the original TBX interpretive language program and trying to encode it in such a way that makes sense on the Arduino.
You may recall the original TBX IL program:
STRT: INIT
ERRENT: NLINE
CO: GETLN
; NLINE
TSTL DIR
INSRT
IJMP CO
XEC: XINIT
DIR: TST S1,'LET'
ICALL AVTEST
ICALL EXPR1
STORE
DONE
NXT
S1: TST S3,'GO'
TST S2,'TO'
ICALL EXPR
DONE
XFER
S2: TST S14,'SUB'
ICALL EXPR
DONEX
SAV
XFER
S3: TST S3A,'IF'
ICALL EXPR
ICALL RELOP
ICALL EXPR
CMPR
IJMP DIR
S3A: TST S4A,'FOR'
TSTV S3ERR
ICALL EXPR1
STORE
TST S3ERR,'TO' ; DDJ V1N2, p.36 #1
FOR
ICALL EXPR
DONE
NXT
S3ERR: ERR 16
S4: TST S9,'PR'
S5: TST S8C,'"'
PRS
S6: TST S7A,','
SPC
S7B: TST S5A,13
NXT
S7A: TST S7,59 ; semicolon
SPCONE
IJMP S7B
S7: NLINE
DONE
NXT
S8: ICALL EXPR
PRN
IJMP S6
S8A: DONE
NXT
S9: TST S12,'IN'
S10: ICALL AVTEST
INNUM
STORE
TST S11,','
IJMP S10
S11: TST S7,59 ; DDJ V1N2, p.36 #2
DONE
NXT
S12: TST S13,'RET'
DONE
RSTR
NXT
S13: TST S14,'END'
NLINE
FIN
S18: TST S15,'LST'
IJMP S18A
S15: TST S16,'RUN'
DONE
IJMP XEC
S16: TST S17A,'NEW'
DONE
IJMP STRT
S17: ERR 12
S5A: TST S5,'$'
NXTX
EXPR1: TST EXPR,'='
EXPR: TST E0,'-'
ICALL TERM
NEG
IJMP E1
E0: TST E3,'+'
E3: ICALL TERM
E1: TST E2,'+'
ICALL TERM
ADD
IJMP E1
E2: TST E4,'-'
ICALL TERM
SUB
IJMP E1
TERM: ICALL FACT
T0: TST T1,'*'
ICALL FACT
MPY
IJMP T0
T1: TST T2,'/'
ICALL FACT
DIV
IJMP T0
FACT: TSTF F4
IJMP FN
F4: TSTA F0
ICALL ARRAY
IND
RTN
F0: TSTV F1
IND
T2: RTN
E4: RTN
F1: TSTN F2
RTN
F2: TST F3,'('
ICALL EXPR
TST F3,')'
RTN
F3: ERR 13
S17A: TST S17,'SZE'
SIZE
IJMP S8A
RELOP: TST R0,'='
LIT 0
RTN
R0: TST R4,'<'
TST R1,'='
LIT 2
RTN
R1: TST R3,'>'
LIT 3
RTN
R3: LIT 1
RTN
R4: TST S17,'>'
TST R5,'='
LIT 5
RTN
R5: TST R6,'<'
LIT 3
RTN
R6: LIT 4
RTN
S14: TST S18,'DIM'
Z0: TSTV ZERR
TST F3,'('
ICALL EXPR
TST Z1,','
ICALL EXPR
TST F3,')'
DIM2
Z3: TST Z2,','
IJMP Z0
Z2: DONE
NXT
Z1: TST F3,')'
DIM1
IJMP Z3
ZERR: ERR 13
ARRAY: TST F3,'('
ICALL EXPR
TST X0,','
ICALL EXPR
TST F3,')'
ARRAY2
RTN
X0: TST F3,')'
ARRAY1
RTN
AVTEST: TSTA V0
ICALL ARRAY
RTN
V0: TSTV VERR
RTN
VERR: ERR 11
S4A: TST S4,'NXT'
TSTV S4ERR
NEXT
STORE
DONE
NXT
S4ERR: ERR 14
FN: TST FN0,'RN'
RANDOM
RTN
FN0: TST S17,'SP' ; DDJ V1N2, p.36 #6
ICALL EXPR
TAB
RTN
S8C: TST S8B,13
NLINE
NXT
S8B: TST S8,'$'
NLINE
NXTX
S18A: TST S18B,13
LIST0
LST
NXT
S18B: ICALL EXPR
TST S18C,','
ICALL EXPR
LIST2
LST
IJMP S8A
S18C: LIST1
LST
IJMP S8A
Each of these IL instructions need to be written out in Arduino instructions in assembly language. So, for example, TST, ICALL, etc.
The original TBX 8080 code dealt with this problem like this:
;
; INTERPRETIVE LANGUAGE PROGRAM
;
; 0b00xxxxxx JUMP TO IL INSTRUCTION AT 00xxxxxx
; 0b01xxxxxx CALL IL SUBROUTINE AT 00xxxxxx
; 0b10xxxxxx COMPARE TO STRING FAIL TO IL AT 00xxxxxx
; 0b11xxxxxx CALL ML SUBROUTINE AT 00xxxxxx
;
NEWPRG: DB hi(INIT) | 0b11000000, lo(INIT) ;CALL INIT
NEWLNE: DB hi(NLINE) | 0b11000000, lo(NLINE) ;CALL NLINE
DB hi(GETLINE) | 0b11000000, lo(GETLINE) ;CALL GETLINE
DB hi(NLINE) | 0b11000000, lo(NLINE) ;CALL NLINE
DB hi(TSTL) | 0b11000000, lo(TSTL) ;CALL TSTL
DB hi(DIR) | 0b00000000, lo(DIR) ;JUMP DIR
DB hi(INSRT) | 0b11000000, lo(INSRT) ;CALL INSRT
DB hi(NEWLNE) | 0b00000000, lo(NEWLNE) ;JUMP NEWLNE
;
XEQ: DB hi(XINIT) | 0b11000000, lo(XINIT) ;CALL XINIT
DIR:
;
LET: DB hi(GOTO) | 0b10000000, lo(GOTO) ;TST <>'LET' THEN JUMP GOTO
continued...
It grouped each IL instruction into four kinds of opcodes, and applied a mask to the top two bits of each instruction. So when the Arduino program counter goes looking for the label’s address in order to process the IL opcode in the Arduino’s own machine language, it dispatches the control of the IL program using those top two bits.
Now I have the luxury of being able to address more than 16K of memory. In fact I’m only confining myself to 64KB of 256K of the Arduino Mega2560’s flash.
But I solved the problem like this, with macros:
;; IL Opcodes
.equ IL_JUMP, 0b00000000
.equ IL_CALL, 0b01000000
.equ IL_COMPARE, 0b10000000
.equ ML_CALL, 0b11000000
.macro IJMP label
.byte IL_JUMP
.word \label
.endm
.macro ICALL label
.byte IL_CALL
.word \label
.endm
.macro TST label, str, lastchar
.byte IL_COMPARE
.word \label
.ascii "\str"
.byte \lastchar+0x80
.endm
.macro CALL label
.byte ML_CALL
.word \label
.endm
.macro TSTN label
.byte IL_COMPARE
.word \label
.endm
.macro TSTV label
.byte IL_COMPARE
.word \label
.endm
.macro TSTA label
.byte IL_COMPARE
.word \label
.endm
.macro TSTF label
.byte IL_COMPARE
.word \label
.endm
.macro LIT num
CALL LIT\num
.endm
.macro ERR num
CALL ERR\num
.endm
And the IL program gets assembled like this:
/**
* ┬ ┬ ┌─┐┬─┐┌─┐┌─┐┬─┐┌─┐┌┬┐
* │ │ ├─┘├┬┘│ ││ ┬├┬┘├─┤│││
* ┴ ┴─┘ ┴ ┴└─└─┘└─┘┴└─┴ ┴┴ ┴
*/
;; IL PROGRAM
;; $9000 - $BFFF
;; Twelve kilobytes.
;; 0b00xxxxxx Jump to IL instruction @ 00xxxxxx.
;; 0b01xxxxxx Call IL subroutine at 00xxxxxx.
;; 0b10xxxxxx Compare to string, fail to IL @ 00xxxxxx.
;; 0b11xxxxxx Call ML subroutine @ 00xxxxxx.
.org 0x9000
ILSTRT:
NEWPRG:
CALL INIT
ERRENT:
CALL NLINE
CO:
CALL GETLINE
CALL NLINE
CALL TSTL
IJMP DIR
CALL INSRT
IJMP CO
XEC:
CALL XINIT
DIR:
TST S1, "LET", 'T'
continued...
The only difference is that I now reserve a whole byte for that tiny little bitmask.
You can see how treating everything as whole bytes is much easier. Looking at the ILXQT routine inside dandelion.S, which I am currently working on, you can see the first byte of the IL instruction is the bitmask, then the next two bytes define the 16-bit label address of the ML routine defining the IL opcode, and, then, for particularly complex instructions like TST, extra arguments.
;; Compare string (TST instruction).
;; Format: 0b10000000, lo8(label), hi8(label), string, lastchar+0x80
Anyway, that’s everything to report. Ask me anything.