1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-26 09:18:05 +11:00

Compare commits

..

No commits in common. "e17dc1e1e1ff9215a55f400cc55f93e162c08f62" and "3f3dd9141eaa993f202802ed6762847b54780c6f" have entirely different histories.

9 changed files with 16 additions and 415 deletions

View File

@ -11,6 +11,10 @@ reuse those bits of code.
Integrating an existing BASIC to Collapse OS seemed a bigger challenge than
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,
not break anything, and have the wisdom to see when copy/pasting is a better
idea.
## Design goal
The reason for including a BASIC dialect in Collapse OS is to supply some form
@ -24,81 +28,3 @@ 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.
## Usage
Upon launch, a prompt is presented, waiting for a command. There are two types
of command invocation: direct and numbered.
A direct command is executed immediately. Example: `print 42` will print `42`
immediately.
A numbered command is added to BASIC's code listing at the specified line
number. For example, `10 print 42` will set line 10 to the string `print 42`.
Code listing can be printed with `list` and can be ran with `run`. The listing
is kept in order of lines. Line number don't need to be sequential. You can
keep leeway in between your lines and then insert a line with a middle number
later.
Some commands take arguments. Those are given by typing a whitespace after the
command name and then the argument. Additional arguments are given the same way,
by typing a whitespace.
### Numbers, expressions and variables
Only 16-bit integers (unsigned for now) are supported in this BASIC. When
printed, they're printed in decimal form. When expressing number literals, you
can do so either in decimal (`42`), hexadecimal (`0x2a`), binary (`0b101010`)
or char ('a', resulting in number 97).
Expressions are accepted wherever a number is expected. For example,
`print 2+3` will print `5`. Expressions can't have whitespace inside them and
don't support (yet) parentheses. Supported operators are `+`, `-`, `*` and `/`.
Inside a `if` command, "truth" expressions are accepted (`=`, `<`, `>`, `<=`,
`>=`). A thruth expression that doesn't contain a truth operator evaluates the
number as-is: zero if false, nonzero is true.
There are 26 one-letter variables in BASIC which can be assigned a 16-bit
integer to them. You assign a value to a variable with `=`. For example,
`a=42+4` will assign 46 to `a` (case insensitive). Those variables can then
be used in expressions. For example, `print a-6` will print `40`. All variables
are initialized to zero on launch.
### Commands
There are two types of commands: normal and direct-only. The latter can only
be invoked in direct mode, not through a code listing.
**bye**. Direct-only. Quits BASIC
**list**. Direct-only. Prints all lines in the code listing, prefixing them
with their associated line number.
**run**. Direct-only. Runs code from the listing, starting with the first one.
If `goto` was previously called in direct mode, we start from that line instead.
**print**. Prints the result of the specified expression, then CR/LF. Can be
given multiple arguments. In that case, all arguments are printed separately
with a space in between. For example, `print 12 13` prints `12 13<cr><lf>`
Unlike anywhere else, the `print` command can take a string inside a double
quote. That string will be printed as-is. For example, `print "foo" 40+2` will
print `foo 42`.
**goto**. Make the next line to be executed the line number specified as an
argument. Errors out if line doesn't exist. Argument can be an expression. If
invoked in direct mode, `run` must be called to actually run the line (followed
by the next, and so on).
**if**. If specified condition is true, execute the rest of the line. Otherwise,
do nothing. For example, `if 2>1 print 12` prints `12` and `if 2<1 print 12`
does nothing. The argument for this command is a "thruth expression".
**input**. Prompts the user for a numerical value and puts that value in the
specified variable. The prompted value is evaluated as an expression and then
stored where specified. For example, `input x` stores the result of the
evaluation in variable `x`. Before the variable name, a quoted string literal
can be specified. In that case, that string will be printed as-is just before
the prompt.

View File

@ -20,8 +20,6 @@
.inc "lib/fmt.asm"
.equ EXPR_PARSE parseLiteralOrVar
.inc "lib/expr.asm"
.inc "basic/util.asm"
.inc "basic/parse.asm"
.inc "basic/tok.asm"
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
.inc "basic/var.asm"

View File

@ -176,31 +176,21 @@ basRUN:
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
ld de, SCRATCHPAD
call rdWord
push hl ; --> lvl 1
ex de, hl
call parseExpr
jr nz, .parseError
ret nz
push ix \ pop de
ld hl, SCRATCHPAD
call fmtDecimal
call printstr
pop hl ; <-- lvl 1
.chkAnother:
; Do we have another arg?
call rdSep
jr z, .another
; no, we can stop here
.end:
cp a ; ensure Z
jp printcrlf
.another:
@ -208,11 +198,6 @@ basPRINT:
ld a, ' '
call stdioPutC
jr basPRINT
.parseError:
; unwind the stack before returning
pop hl ; <-- lvl 1
ret
basGOTO:
ld de, SCRATCHPAD
@ -233,40 +218,6 @@ basGOTO:
ld (BAS_PNEXTLN), de
ret
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
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
; direct only
basCmds1:
.dw basBYE
@ -281,8 +232,4 @@ basCmds2:
.db "print", 0
.dw basGOTO
.db "goto", 0, 0
.dw basIF
.db "if", 0, 0, 0, 0
.dw basINPUT
.db "input", 0
.db 0xff, 0xff, 0xff ; end of table

View File

@ -1,143 +0,0 @@
; Parse an expression yielding a truth value from (HL) and set A accordingly.
; 0 for False, nonzero for True.
; How it evaluates truth is that it looks for =, <, >, >= or <= in (HL) and,
; if it finds it, evaluate left and right expressions separately. Then it
; compares both sides and set A accordingly.
; If comparison operators aren't found, the whole string is sent to parseExpr
; and zero means False, nonzero means True.
; **This routine mutates (HL).**
; Z for success.
parseTruth:
push ix
push de
ld a, '='
call .maybeFind
jr z, .foundEQ
ld a, '<'
call .maybeFind
jr z, .foundLT
ld a, '>'
call .maybeFind
jr z, .foundGT
jr .simple
.success:
cp a ; ensure Z
.end:
pop de
pop ix
ret
.maybeFind:
push hl ; --> lvl 1
call findchar
jr nz, .notFound
; found! We want to keep new HL around. Let's pop old HL in DE
pop de ; <-- lvl 1
ret
.notFound:
; not found, restore HL
pop hl ; <-- lvl 1
ret
.simple:
call parseExpr
jr nz, .end
push ix \ pop de
ld a, d
or e
jr .success
.foundEQ:
; we found an '=' char and HL is pointing to it. DE is pointing to the
; beginning of our string. Let's separate those two strings.
; But before we do that, to we have a '<' or a '>' at the left of (HL)?
dec hl
ld a, (hl)
cp '<'
jr z, .foundLTE
cp '>'
jr z, .foundGTE
inc hl
; Ok, we are a straight '='. Proceed.
call .splitLR
; HL now point to right-hand, DE to left-hand
call .parseLeftRight
jr nz, .end ; error, stop
xor a ; clear carry and prepare value for False
sbc hl, de
jr nz, .success ; NZ? equality not met. A already 0, return.
; Z? equality met, make A=1, set Z
inc a
jr .success
.foundLTE:
; Almost the same as '<', but we have two sep chars
call .splitLR
inc hl ; skip the '=' char
call .parseLeftRight
jr nz, .end
ld a, 1 ; prepare for True
sbc hl, de
jr nc, .success ; Left <= Right, True
; Left > Right, False
dec a
jr .success
.foundGTE:
; Almost the same as '<='
call .splitLR
inc hl ; skip the '=' char
call .parseLeftRight
jr nz, .end
ld a, 1 ; prepare for True
sbc hl, de
jr z, .success ; Left == Right, True
jr c, .success ; Left > Right, True
; Left < Right, False
dec a
jr .success
.foundLT:
; Same thing as EQ, but for '<'
call .splitLR
call .parseLeftRight
jr nz, .end
xor a
sbc hl, de
jr z, .success ; Left == Right, False
jr c, .success ; Left > Right, False
; Left < Right, True
inc a
jr .success
.foundGT:
; Same thing as EQ, but for '>'
call .splitLR
call .parseLeftRight
jr nz, .end
xor a
sbc hl, de
jr nc, .success ; Left <= Right, False
; Left > Right, True
inc a
jr .success
.splitLR:
xor a
ld (hl), a
inc hl
ret
; Given string pointers in (HL) and (DE), evaluate those two expressions and
; place their corresponding values in HL and DE.
.parseLeftRight:
; let's start with HL
call parseExpr
ret nz
push ix ; --> lvl 1. save (HL) value in stack.
ex de, hl
call parseExpr
ret nz
push ix \ pop de
pop hl ; <-- lvl 1. restore.
ret

View File

@ -1,15 +0,0 @@
; Is (HL) a double-quoted string? If yes, spit what's inside and place (HL)
; at char after the closing quote.
; Set Z if there was a string, unset otherwise.
spitQuoted:
ld a, (hl)
cp '"'
ret nz
inc hl
.loop:
ld a, (hl)
inc hl
cp '"'
ret z
call stdioPutC
jr .loop

View File

@ -55,8 +55,14 @@ varTryAssign:
call parseExpr ; --> number in IX
jr nz, .exprErr
pop af ; <-- lvl 4
push ix \ pop de ; send number to DE
call varAssign
add a, a ; * 2 because each element is a word
ld hl, VAR_TBL
call addHL
; HL placed, write number
push ix \ pop de
ld (hl), e
inc hl
ld (hl), d
xor a ; ensure Z
.end:
pop de ; <-- lvl 3
@ -67,21 +73,6 @@ varTryAssign:
pop af ; <-- lvl 4
jr .end
; Given a variable **index** in A (call varChk to transform) and a value in
; DE, assign that value in the proper cell in VAR_TBL.
; No checks are made.
varAssign:
push hl
add a, a ; * 2 because each element is a word
ld hl, VAR_TBL
call addHL
; HL placed, write number
ld (hl), e
inc hl
ld (hl), d
pop hl
ret
; Check if value at (HL) is a variable. If yes, returns its associated value.
; Otherwise, jump to parseLiteral.
parseLiteralOrVar:

View File

@ -11,7 +11,6 @@
; *** Code ***
;
; Parse expression in string at (HL) and returns the result in IX.
; **This routine mutates (HL).**
; We expect (HL) to be disposable: we mutate it to avoid having to make a copy.
; Sets Z on success, unset on error.
parseExpr:

View File

@ -19,7 +19,8 @@ fill:
findchar:
push bc
ld c, a ; let's use C as our cp target
ld b, 0xff
ld a, 0xff
ld b, a
.loop: ld a, (hl)
cp c
@ -29,7 +30,7 @@ findchar:
inc hl
djnz .loop
.nomatch:
inc a ; unset Z
call unsetZ
jr .end
.match:
; We ran 0xff-B loops. That's the result that goes in A.

View File

@ -1,103 +0,0 @@
jp test
.inc "core.asm"
.inc "str.asm"
.inc "lib/util.asm"
.inc "lib/parse.asm"
.equ EXPR_PARSE parseLiteral
.inc "lib/expr.asm"
.inc "basic/parse.asm"
test:
ld sp, 0xffff
call testParseThruth
; success
xor a
halt
testParseThruth:
ld hl, .t1
call .true
ld hl, .t2
call .true
ld hl, .t3
call .true
ld hl, .t4
call .true
ld hl, .t5
call .true
ld hl, .t6
call .true
ld hl, .t7
call .true
ld hl, .t8
call .true
ld hl, .f1
call .false
ld hl, .f2
call .false
ld hl, .f3
call .false
ld hl, .f4
call .false
ld hl, .f5
call .false
ld hl, .f6
call .false
ld hl, .e1
call .error
ret
.true:
call parseTruth
jp nz, fail
or a
jp z, fail
jp nexttest
.false:
call parseTruth
jp nz, fail
or a
jp nz, fail
jp nexttest
.error:
call parseTruth
jp z, fail
jp nexttest
.t1: .db "42", 0
.t2: .db "42+4=50-4", 0
.t3: .db "1<2", 0
.t4: .db "2>1", 0
.t5: .db "2>=1", 0
.t6: .db "2>=2", 0
.t7: .db "1<=2", 0
.t8: .db "2<=2", 0
.f1: .db "42-42", 0
.f2: .db "42+4=33+2", 0
.f3: .db "2<2", 0
.f4: .db "1>2", 0
.f5: .db "1>=2", 0
.f6: .db "2<=1", 0
.e1: .db "foo", 0
testNum: .db 1
nexttest:
ld a, (testNum)
inc a
ld (testNum), a
ret
fail:
ld a, (testNum)
halt
; used as RAM
sandbox: