collapseos/apps/basic/main.asm

338 lines
5.8 KiB
NASM
Raw Normal View History

; *** Variables ***
; Value of `SP` when basic was first invoked. This is where SP is going back to
; on restarts.
.equ BAS_INITSP BAS_RAMSTART
; 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
; *** Code ***
basStart:
ld (BAS_INITSP), sp
call varInit
call bufInit
xor a
2019-11-21 12:58:26 +11:00
ld (BAS_PNEXTLN), a
ld (BAS_PNEXTLN+1), a
ld hl, .welcome
call printstr
call printcrlf
2019-11-21 02:49:23 +11:00
jr basLoop
.welcome:
.db "OK", 0
2019-11-21 02:49:23 +11:00
basLoop:
ld hl, .sPrompt
call printstr
call stdioReadLine
2019-11-21 02:49:23 +11:00
call printcrlf
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
.number:
push ix \ pop de
2019-11-21 12:58:26 +11:00
call toSep
call rdSep
call bufAdd
jp nz, basERR
2019-11-21 02:49:23 +11:00
jr basLoop
.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:
; let's see if it's a variable assignment.
call varTryAssign
ret z ; Done!
; Second, get cmd length
call fnWSIdx
cp 7
2019-11-21 02:49:23 +11:00
jp nc, unsetZ ; Too long, can't possibly fit anything.
; A contains whitespace IDX, save it in B
ld b, a
ex de, hl
2019-11-21 02:49:23 +11:00
inc hl \ inc hl
.loop:
ld a, b ; whitespace IDX
call strncmp
jr z, .found
ld a, 8
call addHL
ld a, (hl)
cp 0xff
jr nz, .loop
2019-11-21 02:49:23 +11:00
; not found
jp unsetZ
.found:
dec hl \ dec hl
call intoHL
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
jp (ix)
basERR:
ld hl, .sErr
2019-11-21 12:58:26 +11:00
call printstr
jp printcrlf
.sErr:
.db "ERR", 0
; *** Commands ***
; 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.
basBYE:
ld hl, .sBye
2019-11-21 12:58:26 +11:00
call printstr
call printcrlf
; 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
basLIST:
call bufFirst
ret nz
.loop:
ld e, (ix)
ld d, (ix+1)
2019-11-21 12:58:26 +11:00
ld hl, SCRATCHPAD
call fmtDecimal
call printstr
ld a, ' '
call stdioPutC
2019-11-20 12:43:01 +11:00
call bufStr
call printstr
call printcrlf
call bufNext
jr z, .loop
2019-11-21 02:49:23 +11:00
cp a ; ensure Z
ret
basRUN:
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
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
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
; 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
basPRINT:
; 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
call parseExpr
jr nz, .parseError
push ix \ pop de
2019-11-21 12:58:26 +11:00
ld hl, SCRATCHPAD
2019-11-24 06:56:23 +11:00
call fmtDecimalS
2019-11-21 12:58:26 +11:00
call printstr
pop hl ; <-- lvl 1
.chkAnother:
2019-11-21 12:58:26 +11:00
; Do we have another arg?
call rdSep
jr z, .another
; no, we can stop here
.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
.parseError:
; unwind the stack before returning
pop hl ; <-- lvl 1
ret
basGOTO:
2019-11-21 12:58:26 +11:00
ld de, SCRATCHPAD
call rdWord
ex de, hl
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
basPEEK:
call basDEEK
ret nz
ld d, 0
call varAssign
ret
basPOKE:
call rdExpr
ret nz
; peek address in IX. Save it for later
push ix ; --> lvl 1
call rdSep
call rdExpr
push ix \ pop hl
pop ix ; <-- lvl 1
ret nz
; Poke!
ld (ix), l
ret
basDEEK:
call rdExpr
ret nz
; peek address in IX. Let's peek and put result in DE
ld e, (ix)
ld d, (ix+1)
call rdSep
ld a, (hl)
call varChk
ret nz ; not in variable range
; All good assign
call varAssign
cp a ; ensure Z
ret
basDOKE:
call basPOKE
ld (ix+1), h
ret
; direct only
basCmds1:
.dw basBYE
.db "bye", 0, 0, 0
.dw basLIST
.db "list", 0, 0
2019-11-21 02:49:23 +11:00
.dw basRUN
.db "run", 0, 0, 0
; statements
basCmds2:
.dw basPRINT
.db "print", 0
.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
.dw basPEEK
.db "peek", 0, 0
.dw basPOKE
.db "poke", 0, 0
.dw basDEEK
.db "deek", 0, 0
.dw basDOKE
.db "doke", 0, 0
.db 0xff, 0xff, 0xff ; end of table