1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 18:20:55 +11:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Virgil Dupras
62138b12cf basic: add buffer line index 2019-11-19 20:43:01 -05:00
Virgil Dupras
4c6de413df basic: begin implementing a line buffer 2019-11-19 15:14:04 -05:00
6 changed files with 198 additions and 6 deletions

27
CODE.md
View File

@ -51,6 +51,25 @@ Therefore, shadow registers should only be used in code that doesn't call
routines or that call a routine that explicitly states that it preserves routines or that call a routine that explicitly states that it preserves
shadow registers. shadow registers.
Another important note is that routines returning success with Z generally don't
preserve AF: too complicated. But otherwise, AF is often preserved. For example,
register fiddling routines in core try to preserve AF.
## Z for success
The vast majority of routines use the Z flag to indicate success. When Z is set,
it indicates success. When Z is unset, it indicates error. This follows the
tradition of a zero indicating success and a nonzero indicating error.
Important note: only Z indicate success. Many routines return a meaningful
nonzero value in A and still set Z to indicate success.
In error conditions, however, most of the time A is set to an error code.
In many routines, this is specified verbosely, but it's repeated so often that
I started writing it in short form, "Z for success", which means what is
described here.
## Stack management ## Stack management
Keeping the stack "balanced" is a big challenge when writing assembler code. Keeping the stack "balanced" is a big challenge when writing assembler code.
@ -87,4 +106,12 @@ comments. Example:
pop af ; <-- lvl 1 pop af ; <-- lvl 1
I think that this should do the trick, so I'll do this consistently from now on. I think that this should do the trick, so I'll do this consistently from now on.
## String length
Pretty much every routine expecting a string have no provision for a string
that doesn't have null termination within 0xff bytes. Treat strings of such
lengths with extra precaution and distrust proper handling of existing routines
for those strings.
[zasm]: ../apps/zasm/README.md [zasm]: ../apps/zasm/README.md

View File

@ -14,3 +14,17 @@ writing from scratch, so here I am, writing from scratch again...
The biggest challenge here is to extract code from zasm, adapt it to fit BASIC, The biggest challenge here is to extract code from zasm, adapt it to fit BASIC,
not break anything, and have the wisdom to see when copy/pasting is a better not break anything, and have the wisdom to see when copy/pasting is a better
idea. idea.
## Design goal
The reason for including a BASIC dialect in Collapse OS is to supply some form
of system administration swiss knife. zasm, ed and the shell can do
theoretically anything, but some tasks (which are difficult to predict) can
possibly be overly tedious. One can think, for example, about hardware
debugging. Poking and peeking around when not sure what we're looking for can
be a lot more effective with the help of variables, conditions and for-loops in
an interpreter.
Because the goal is not to provide a foundation for complex programs, I'm
planning on intentionally crippling this BASIC dialect for the sake of
simplicity.

117
apps/basic/buf.asm Normal file
View File

@ -0,0 +1,117 @@
; *** Consts ***
; maximum number of lines (line number maximum, however, is always 0xffff)
.equ BUF_MAXLINES 0x100
; Size of the string pool
.equ BUF_POOLSIZE 0x1000
; *** Variables ***
; A pointer to the first free line
.equ BUF_LFREE BUF_RAMSTART
; A pointer to the first free byte in the pool
.equ BUF_PFREE @+2
; The line index. Each record consists of 4 bytes: 2 for line number,
; 2 for pointer to string in pool. Kept in order of line numbers.
.equ BUF_LINES @+2
; The line pool. A list of null terminated strings. BUF_LINES records point
; to those strings.
.equ BUF_POOL @+BUF_MAXLINES*4
.equ BUF_RAMEND @+BUF_POOLSIZE
bufInit:
ld hl, BUF_LINES
ld (BUF_LFREE), hl
ld hl, BUF_POOL
ld (BUF_PFREE), hl
ret
; Add line at (HL) with line number DE to the buffer. The string at (HL) should
; not contain the line number prefix or the whitespace between the line number
; and the comment.
; Note that an empty string is *not* an error. It will be saved as a line.
; Z for success.
; Error conditions are:
; * not enough space in the pool
; * not enough space in the line index
bufAdd:
exx ; preserve HL and DE
; First step: do we have index space?
ld hl, (BUF_LFREE)
ld de, BUF_POOL
or a ; reset carry
sbc hl, de
exx ; restore
; no carry? HL >= BUF_POOL, error. Z already unset
ret nc
; Second step: see if we're within the pool's bounds
call strlen
inc a ; strlen doesn't include line termination
exx ; preserve HL and DE
ld hl, (BUF_PFREE)
call addHL
ld de, BUF_RAMEND
sbc hl, de
exx ; restore
; no carry? HL >= BUF_RAMEND, error. Z already unset
ret nc
; We have enough space.
; Third step: set line index data
push de ; --> lvl 1
push hl ; --> lvl 2
ld hl, (BUF_LFREE)
ld (hl), e
inc hl
ld (hl), d
inc hl
ld de, (BUF_PFREE)
ld (hl), e
inc hl
ld (hl), d
inc hl
ld (BUF_LFREE), hl
pop hl \ push hl ; <-- lvl 2, restore and preserve
; Fourth step: copy string to pool
ld de, (BUF_PFREE)
call strcpyM
ld (BUF_PFREE), de
pop hl ; <-- lvl 2
pop de ; <-- lvl 1
ret
; Set IX to point to the beginning of the pool.
; Z set if (IX) is a valid line, unset if the pool is empty.
bufFirst:
ld ix, BUF_LINES
jp bufEOF
; Given a valid line record in IX, move IX to the next line.
; This routine doesn't check that IX is valid. Ensure IX validity before
; calling.
bufNext:
inc ix \ inc ix \ inc ix \ inc ix
jp bufEOF
; Returns whether line index at IX is past the end of file, that is,
; whether IX == (BUF_LFREE)
; Z is set when not EOF, unset when EOF.
bufEOF:
push hl
push de
push ix \ pop hl
ld de, (BUF_LFREE)
sbc hl, de
jr z, .empty
cp a ; ensure Z
.end:
pop de
pop hl
ret
.empty:
call unsetZ
jr .end
; Given a line index in (IX), set HL to its associated string pointer.
bufStr:
ld l, (ix+2)
ld h, (ix+3)
ret

View File

@ -17,6 +17,8 @@
.equ EXPR_PARSE parseLiteral .equ EXPR_PARSE parseLiteral
.inc "lib/expr.asm" .inc "lib/expr.asm"
.inc "basic/tok.asm" .inc "basic/tok.asm"
.equ BAS_RAMSTART USER_RAMSTART .equ BUF_RAMSTART USER_RAMSTART
.inc "basic/buf.asm"
.equ BAS_RAMSTART BUF_RAMEND
.inc "basic/main.asm" .inc "basic/main.asm"
USER_RAMSTART: USER_RAMSTART:

View File

@ -12,6 +12,7 @@
; *** Code *** ; *** Code ***
basStart: basStart:
ld (BAS_INITSP), sp ld (BAS_INITSP), sp
call bufInit
xor a xor a
ld hl, .welcome ld hl, .welcome
call printstr call printstr
@ -32,12 +33,13 @@ basPrompt:
call basDirect call basDirect
jr basPrompt jr basPrompt
.number: .number:
; do nothing for now, we only support direct mode. push ix \ pop de
ld hl, .sNumber call toWS
call basPrintLn call rdWS
call bufAdd
jp nz, basERR
call printcrlf
jr basPrompt jr basPrompt
.sNumber:
.db "A number!", 0
.sPrompt: .sPrompt:
.db "> ", 0 .db "> ", 0
@ -104,6 +106,26 @@ basBYE:
.sBye: .sBye:
.db "Goodbye!", 0 .db "Goodbye!", 0
basLIST:
call printcrlf
call bufFirst
ret nz
.loop:
ld e, (ix)
ld d, (ix+1)
ld hl, BAS_SCRATCHPAD
call fmtDecimal
call printstr
ld a, ' '
call stdioPutC
call bufStr
call printstr
call printcrlf
call bufNext
jr z, .loop
ret
basPRINT: basPRINT:
call parseExpr call parseExpr
jp nz, basERR jp nz, basERR
@ -116,6 +138,8 @@ basPRINT:
basCmds1: basCmds1:
.dw basBYE .dw basBYE
.db "bye", 0, 0, 0 .db "bye", 0, 0, 0
.dw basLIST
.db "list", 0, 0
; statements ; statements
basCmds2: basCmds2:
.dw basPRINT .dw basPRINT

View File

@ -46,3 +46,11 @@ fnWSIdx:
pop bc pop bc
pop hl pop hl
ret ret
; Advance HL to the next whitespace or to the end of string.
toWS:
ld a, (hl)
call isSep
ret z
inc hl
jr toWS