2019-11-14 07:28:16 +11:00
|
|
|
; *** Variables ***
|
|
|
|
; Value of `SP` when basic was first invoked. This is where SP is going back to
|
|
|
|
; on restarts.
|
|
|
|
.equ BAS_INITSP BAS_RAMSTART
|
2019-11-21 07:44:13 +11:00
|
|
|
; Pointer to next line to run. If nonzero, it means that the next line is
|
|
|
|
; the first of the list. This is used by GOTO to indicate where to jump next.
|
|
|
|
; Important note: this is **not** a line number, it's a pointer to a line index
|
|
|
|
; in buffer. If it's not zero, its a valid pointer.
|
|
|
|
.equ BAS_PNEXTLN @+2
|
2019-11-21 12:58:26 +11:00
|
|
|
.equ BAS_RAMEND @+2
|
2019-11-14 07:28:16 +11:00
|
|
|
|
|
|
|
; *** Code ***
|
|
|
|
basStart:
|
|
|
|
ld (BAS_INITSP), sp
|
2019-11-21 07:10:00 +11:00
|
|
|
call varInit
|
2019-11-20 07:14:04 +11:00
|
|
|
call bufInit
|
2019-11-14 07:28:16 +11:00
|
|
|
xor a
|
2019-11-21 12:58:26 +11:00
|
|
|
ld (BAS_PNEXTLN), a
|
|
|
|
ld (BAS_PNEXTLN+1), a
|
2019-11-14 07:28:16 +11:00
|
|
|
ld hl, .welcome
|
|
|
|
call printstr
|
|
|
|
call printcrlf
|
2019-11-21 02:49:23 +11:00
|
|
|
jr basLoop
|
2019-11-14 07:28:16 +11:00
|
|
|
|
|
|
|
.welcome:
|
2019-11-21 07:44:13 +11:00
|
|
|
.db "OK", 0
|
2019-11-14 07:28:16 +11:00
|
|
|
|
2019-11-21 02:49:23 +11:00
|
|
|
basLoop:
|
2019-11-14 07:28:16 +11:00
|
|
|
ld hl, .sPrompt
|
|
|
|
call printstr
|
|
|
|
call stdioReadLine
|
2019-11-21 02:49:23 +11:00
|
|
|
call printcrlf
|
2019-11-14 07:28:16 +11:00
|
|
|
call parseDecimal
|
|
|
|
jr z, .number
|
2019-11-21 02:49:23 +11:00
|
|
|
ld de, basCmds1
|
|
|
|
call basCallCmd
|
|
|
|
jr z, basLoop
|
|
|
|
; Error
|
|
|
|
call basERR
|
|
|
|
jr basLoop
|
2019-11-14 07:28:16 +11:00
|
|
|
.number:
|
2019-11-20 07:14:04 +11:00
|
|
|
push ix \ pop de
|
2019-11-21 12:58:26 +11:00
|
|
|
call toSep
|
|
|
|
call rdSep
|
2019-11-20 07:14:04 +11:00
|
|
|
call bufAdd
|
|
|
|
jp nz, basERR
|
2019-11-21 02:49:23 +11:00
|
|
|
jr basLoop
|
2019-11-14 07:28:16 +11:00
|
|
|
.sPrompt:
|
|
|
|
.db "> ", 0
|
|
|
|
|
2019-11-21 02:49:23 +11:00
|
|
|
; Call command in (HL) after having looked for it in cmd table in (DE).
|
|
|
|
; If found, jump to it. If not found, unset Z. We expect commands to set Z
|
|
|
|
; on success. Therefore, when calling basCallCmd results in NZ, we're not sure
|
|
|
|
; where the error come from, but well...
|
|
|
|
basCallCmd:
|
2019-11-21 07:10:00 +11:00
|
|
|
; let's see if it's a variable assignment.
|
|
|
|
call varTryAssign
|
|
|
|
ret z ; Done!
|
|
|
|
; Second, get cmd length
|
2019-11-19 05:40:23 +11:00
|
|
|
call fnWSIdx
|
|
|
|
cp 7
|
2019-11-21 02:49:23 +11:00
|
|
|
jp nc, unsetZ ; Too long, can't possibly fit anything.
|
2019-11-19 05:40:23 +11:00
|
|
|
; A contains whitespace IDX, save it in B
|
|
|
|
ld b, a
|
2019-11-14 07:28:16 +11:00
|
|
|
ex de, hl
|
2019-11-21 02:49:23 +11:00
|
|
|
inc hl \ inc hl
|
2019-11-14 07:28:16 +11:00
|
|
|
.loop:
|
2019-11-19 05:40:23 +11:00
|
|
|
ld a, b ; whitespace IDX
|
2019-11-14 07:28:16 +11:00
|
|
|
call strncmp
|
|
|
|
jr z, .found
|
2019-11-19 05:40:23 +11:00
|
|
|
ld a, 8
|
2019-11-14 07:28:16 +11:00
|
|
|
call addHL
|
|
|
|
ld a, (hl)
|
|
|
|
cp 0xff
|
|
|
|
jr nz, .loop
|
2019-11-21 02:49:23 +11:00
|
|
|
; not found
|
|
|
|
jp unsetZ
|
2019-11-14 07:28:16 +11:00
|
|
|
.found:
|
2019-11-19 05:40:23 +11:00
|
|
|
dec hl \ dec hl
|
2019-11-14 07:28:16 +11:00
|
|
|
call intoHL
|
2019-11-19 05:40:23 +11:00
|
|
|
push hl \ pop ix
|
|
|
|
; Bring back command string from DE to HL
|
|
|
|
ex de, hl
|
|
|
|
ld a, b ; cmd's length
|
|
|
|
call addHL
|
2019-11-21 12:58:26 +11:00
|
|
|
call rdSep
|
2019-11-19 05:40:23 +11:00
|
|
|
jp (ix)
|
2019-11-14 07:28:16 +11:00
|
|
|
|
2019-11-19 05:40:23 +11:00
|
|
|
basERR:
|
|
|
|
ld hl, .sErr
|
2019-11-21 12:58:26 +11:00
|
|
|
call printstr
|
|
|
|
jp printcrlf
|
2019-11-19 05:40:23 +11:00
|
|
|
.sErr:
|
|
|
|
.db "ERR", 0
|
|
|
|
|
2019-11-14 07:28:16 +11:00
|
|
|
; *** Commands ***
|
2019-11-19 05:40:23 +11:00
|
|
|
; A command receives its argument through (HL), which is already placed to
|
|
|
|
; either:
|
|
|
|
; 1 - the end of the string if the command has no arg.
|
|
|
|
; 2 - the beginning of the arg, with whitespace properly skipped.
|
2019-11-21 02:49:23 +11:00
|
|
|
;
|
|
|
|
; Commands are expected to set Z on success.
|
2019-11-14 07:28:16 +11:00
|
|
|
basBYE:
|
|
|
|
ld hl, .sBye
|
2019-11-21 12:58:26 +11:00
|
|
|
call printstr
|
|
|
|
call printcrlf
|
2019-11-14 07:28:16 +11:00
|
|
|
; To quit the loop, let's return the stack to its initial value and
|
|
|
|
; then return.
|
|
|
|
xor a
|
|
|
|
ld sp, (BAS_INITSP)
|
|
|
|
ret
|
|
|
|
.sBye:
|
|
|
|
.db "Goodbye!", 0
|
|
|
|
|
2019-11-20 07:14:04 +11:00
|
|
|
basLIST:
|
|
|
|
call bufFirst
|
|
|
|
ret nz
|
|
|
|
.loop:
|
|
|
|
ld e, (ix)
|
|
|
|
ld d, (ix+1)
|
2019-11-21 12:58:26 +11:00
|
|
|
ld hl, SCRATCHPAD
|
2019-11-20 07:14:04 +11:00
|
|
|
call fmtDecimal
|
|
|
|
call printstr
|
|
|
|
ld a, ' '
|
|
|
|
call stdioPutC
|
2019-11-20 12:43:01 +11:00
|
|
|
call bufStr
|
2019-11-20 07:14:04 +11:00
|
|
|
call printstr
|
|
|
|
call printcrlf
|
|
|
|
call bufNext
|
|
|
|
jr z, .loop
|
2019-11-21 02:49:23 +11:00
|
|
|
cp a ; ensure Z
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
basRUN:
|
2019-11-21 07:44:13 +11:00
|
|
|
call .maybeGOTO
|
|
|
|
jr nz, .loop ; IX already set
|
2019-11-21 02:49:23 +11:00
|
|
|
call bufFirst
|
|
|
|
ret nz
|
|
|
|
.loop:
|
|
|
|
call bufStr
|
|
|
|
ld de, basCmds2
|
|
|
|
push ix ; --> lvl 1
|
|
|
|
call basCallCmd
|
|
|
|
pop ix ; <-- lvl 1
|
|
|
|
jp nz, .err
|
2019-11-21 07:44:13 +11:00
|
|
|
call .maybeGOTO
|
|
|
|
jr nz, .loop ; IX already set
|
2019-11-21 02:49:23 +11:00
|
|
|
call bufNext
|
|
|
|
jr z, .loop
|
|
|
|
cp a ; ensure Z
|
2019-11-20 07:14:04 +11:00
|
|
|
ret
|
2019-11-21 02:49:23 +11:00
|
|
|
.err:
|
|
|
|
; Print line number, then return NZ (which will print ERR)
|
|
|
|
ld e, (ix)
|
|
|
|
ld d, (ix+1)
|
2019-11-21 12:58:26 +11:00
|
|
|
ld hl, SCRATCHPAD
|
2019-11-21 02:49:23 +11:00
|
|
|
call fmtDecimal
|
|
|
|
call printstr
|
|
|
|
ld a, ' '
|
|
|
|
call stdioPutC
|
|
|
|
jp unsetZ
|
2019-11-20 07:14:04 +11:00
|
|
|
|
2019-11-21 07:44:13 +11:00
|
|
|
; This returns the opposite Z result as the one we usually see: Z is set if
|
|
|
|
; we **don't** goto, unset if we do. If we do, IX is properly set.
|
|
|
|
.maybeGOTO:
|
|
|
|
ld de, (BAS_PNEXTLN)
|
|
|
|
ld a, d
|
|
|
|
or e
|
|
|
|
ret z
|
|
|
|
; we goto
|
|
|
|
push de \ pop ix
|
|
|
|
; we need to reset our goto marker
|
|
|
|
ld de, 0
|
|
|
|
ld (BAS_PNEXTLN), de
|
|
|
|
ret
|
2019-11-20 07:14:04 +11:00
|
|
|
|
2019-11-19 05:40:23 +11:00
|
|
|
basPRINT:
|
2019-11-22 11:56:51 +11:00
|
|
|
; Do we have arguments at all? if not, it's not an error, just print
|
|
|
|
; crlf
|
|
|
|
ld a, (hl)
|
|
|
|
or a
|
|
|
|
jr z, .end
|
|
|
|
; Is our arg a string literal?
|
|
|
|
call spitQuoted
|
|
|
|
jr z, .chkAnother ; string printed, skip to chkAnother
|
2019-11-21 12:58:26 +11:00
|
|
|
ld de, SCRATCHPAD
|
|
|
|
call rdWord
|
|
|
|
push hl ; --> lvl 1
|
|
|
|
ex de, hl
|
2019-11-19 07:52:07 +11:00
|
|
|
call parseExpr
|
2019-11-22 11:56:51 +11:00
|
|
|
jr nz, .parseError
|
2019-11-19 05:40:23 +11:00
|
|
|
push ix \ pop de
|
2019-11-21 12:58:26 +11:00
|
|
|
ld hl, SCRATCHPAD
|
2019-11-19 05:40:23 +11:00
|
|
|
call fmtDecimal
|
2019-11-21 12:58:26 +11:00
|
|
|
call printstr
|
|
|
|
pop hl ; <-- lvl 1
|
2019-11-22 11:56:51 +11:00
|
|
|
.chkAnother:
|
2019-11-21 12:58:26 +11:00
|
|
|
; Do we have another arg?
|
|
|
|
call rdSep
|
|
|
|
jr z, .another
|
|
|
|
; no, we can stop here
|
2019-11-22 11:56:51 +11:00
|
|
|
.end:
|
2019-11-21 02:49:23 +11:00
|
|
|
cp a ; ensure Z
|
2019-11-21 12:58:26 +11:00
|
|
|
jp printcrlf
|
|
|
|
.another:
|
|
|
|
; Before we jump to basPRINT, let's print a space
|
|
|
|
ld a, ' '
|
|
|
|
call stdioPutC
|
|
|
|
jr basPRINT
|
2019-11-22 11:56:51 +11:00
|
|
|
.parseError:
|
|
|
|
; unwind the stack before returning
|
|
|
|
pop hl ; <-- lvl 1
|
|
|
|
ret
|
|
|
|
|
2019-11-19 05:40:23 +11:00
|
|
|
|
2019-11-21 07:44:13 +11:00
|
|
|
basGOTO:
|
2019-11-21 12:58:26 +11:00
|
|
|
ld de, SCRATCHPAD
|
|
|
|
call rdWord
|
|
|
|
ex de, hl
|
2019-11-21 07:44:13 +11:00
|
|
|
call parseExpr
|
|
|
|
ret nz
|
|
|
|
push ix \ pop de
|
|
|
|
call bufFind
|
|
|
|
jr nz, .notFound
|
|
|
|
push ix \ pop de
|
|
|
|
; Z already set
|
|
|
|
jr .end
|
|
|
|
.notFound:
|
|
|
|
ld de, 0
|
|
|
|
; Z already unset
|
|
|
|
.end:
|
|
|
|
ld (BAS_PNEXTLN), de
|
|
|
|
ret
|
|
|
|
|
2019-11-22 08:06:14 +11:00
|
|
|
basIF:
|
|
|
|
push hl ; --> lvl 1. original arg
|
|
|
|
ld de, SCRATCHPAD
|
|
|
|
call rdWord
|
|
|
|
ex de, hl
|
|
|
|
call parseTruth
|
|
|
|
pop hl ; <-- lvl 1. restore
|
|
|
|
ret nz
|
|
|
|
or a
|
|
|
|
ret z
|
|
|
|
; expr is true, execute next
|
|
|
|
; (HL) back to beginning of args, skip to next arg
|
|
|
|
call toSep
|
|
|
|
call rdSep
|
|
|
|
ld de, basCmds2
|
|
|
|
jp basCallCmd
|
|
|
|
|
2019-11-22 12:17:55 +11:00
|
|
|
basINPUT:
|
|
|
|
; If our first arg is a string literal, spit it
|
|
|
|
call spitQuoted
|
|
|
|
call rdSep
|
|
|
|
ld a, (hl)
|
|
|
|
call varChk
|
|
|
|
ret nz ; not in variable range
|
|
|
|
push af ; --> lvl 1. remember var index
|
|
|
|
call stdioReadLine
|
|
|
|
call parseExpr
|
|
|
|
push ix \ pop de
|
|
|
|
pop af ; <-- lvl 1. restore var index
|
|
|
|
call varAssign
|
|
|
|
call printcrlf
|
|
|
|
cp a ; ensure Z
|
|
|
|
ret
|
|
|
|
|
2019-11-14 07:28:16 +11:00
|
|
|
; direct only
|
|
|
|
basCmds1:
|
|
|
|
.dw basBYE
|
2019-11-19 05:40:23 +11:00
|
|
|
.db "bye", 0, 0, 0
|
2019-11-20 07:14:04 +11:00
|
|
|
.dw basLIST
|
|
|
|
.db "list", 0, 0
|
2019-11-21 02:49:23 +11:00
|
|
|
.dw basRUN
|
|
|
|
.db "run", 0, 0, 0
|
2019-11-14 07:28:16 +11:00
|
|
|
; statements
|
|
|
|
basCmds2:
|
2019-11-19 05:40:23 +11:00
|
|
|
.dw basPRINT
|
|
|
|
.db "print", 0
|
2019-11-21 07:44:13 +11:00
|
|
|
.dw basGOTO
|
|
|
|
.db "goto", 0, 0
|
2019-11-22 08:06:14 +11:00
|
|
|
.dw basIF
|
|
|
|
.db "if", 0, 0, 0, 0
|
2019-11-22 12:17:55 +11:00
|
|
|
.dw basINPUT
|
|
|
|
.db "input", 0
|
2019-11-19 05:40:23 +11:00
|
|
|
.db 0xff, 0xff, 0xff ; end of table
|