mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 14:20:56 +11:00
Compare commits
No commits in common. "fca0e79da371595c8f443be03c4968db466d73bb" and "7390cb18ed913edb4b76a6c513aef579dcf19266" have entirely different histories.
fca0e79da3
...
7390cb18ed
@ -1,6 +1,6 @@
|
|||||||
TARGETS = runbin/runbin forth/forth
|
TARGETS = runbin/runbin forth/forth
|
||||||
# Those Forth source files are in a particular order
|
# Those Forth source files are in a particular order
|
||||||
FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs
|
FORTHSRCS = core.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs
|
||||||
FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%} forth/run.fs
|
FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%} forth/run.fs
|
||||||
OBJS = emul.o libz80/libz80.o
|
OBJS = emul.o libz80/libz80.o
|
||||||
SLATEST = ../tools/slatest
|
SLATEST = ../tools/slatest
|
||||||
|
38
forth/cmp.fs
38
forth/cmp.fs
@ -1,38 +0,0 @@
|
|||||||
( Words useful for complex comparison operations )
|
|
||||||
|
|
||||||
( n1 -- n1 true )
|
|
||||||
: <>{ 1 ;
|
|
||||||
|
|
||||||
( n1 f -- f )
|
|
||||||
: <>} SWAP DROP ;
|
|
||||||
|
|
||||||
|
|
||||||
: _|&
|
|
||||||
( n1 n2 cell )
|
|
||||||
>R >R DUP R> R> ( n1 n1 n2 cell )
|
|
||||||
@ EXECUTE ( n1 f )
|
|
||||||
;
|
|
||||||
|
|
||||||
( n1 f n2 -- n1 f )
|
|
||||||
: _|
|
|
||||||
CREATE , DOES>
|
|
||||||
( n1 f n2 cell )
|
|
||||||
ROT IF 2DROP 1 EXIT THEN ( n1 true )
|
|
||||||
_|&
|
|
||||||
;
|
|
||||||
|
|
||||||
: _&
|
|
||||||
CREATE , DOES>
|
|
||||||
( n1 f n2 cell )
|
|
||||||
ROT NOT IF 2DROP 0 EXIT THEN ( n1 true )
|
|
||||||
_|&
|
|
||||||
;
|
|
||||||
|
|
||||||
( All words below have this signature:
|
|
||||||
n1 f n2 -- n1 f )
|
|
||||||
' = _| |=
|
|
||||||
' = _& &=
|
|
||||||
' > _| |>
|
|
||||||
' > _& &>
|
|
||||||
' < _| |<
|
|
||||||
' < _& &<
|
|
@ -1,11 +1,21 @@
|
|||||||
Be sure to read "usage.txt" for a guide to Collapse OS' Forth.
|
|
||||||
|
|
||||||
*** Glossary ***
|
|
||||||
Stack notation: "<stack before> -- <stack after>". Rightmost is top of stack
|
Stack notation: "<stack before> -- <stack after>". Rightmost is top of stack
|
||||||
(TOS). For example, in "a b -- c d", b is TOS before, d is TOS after. "R:" means
|
(TOS). For example, in "a b -- c d", b is TOS before, d is TOS after. "R:" means
|
||||||
that the Return Stack is modified. "I:" prefix means "IMMEDIATE", that is, that
|
that the Return Stack is modified. "I:" prefix means "IMMEDIATE", that is, that
|
||||||
this stack transformation is made at compile time.
|
this stack transformation is made at compile time.
|
||||||
|
|
||||||
|
DOES>: Used inside a colon definition that itself uses CREATE, DOES> transforms
|
||||||
|
that newly created word into a "does cell", that is, a regular cell ( when
|
||||||
|
called, puts the cell's addr on PS), but right after that, it executes words
|
||||||
|
that appear after the DOES>.
|
||||||
|
|
||||||
|
"does cells" always allocate 4 bytes (2 for the cell, 2 for the DOES> link) and
|
||||||
|
there is no need for ALLOT in colon definition.
|
||||||
|
|
||||||
|
At compile time, colon definition stops processing words when reaching the
|
||||||
|
DOES>.
|
||||||
|
|
||||||
|
Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;"
|
||||||
|
|
||||||
Word references (wordref): When we say we have a "word reference", it's a
|
Word references (wordref): When we say we have a "word reference", it's a
|
||||||
pointer to a words *code link*. For example, the label "PLUS:" in this unit is a
|
pointer to a words *code link*. For example, the label "PLUS:" in this unit is a
|
||||||
word reference. Why not refer to the beginning of the word struct? Because we
|
word reference. Why not refer to the beginning of the word struct? Because we
|
||||||
@ -44,6 +54,20 @@ IMMEDIATE -- Flag the latest defined word as immediate.
|
|||||||
LITN n -- Write number n as a literal.
|
LITN n -- Write number n as a literal.
|
||||||
VARIABLE c -- Creates cell x with 2 bytes allocation.
|
VARIABLE c -- Creates cell x with 2 bytes allocation.
|
||||||
|
|
||||||
|
Compilation vs meta-compilation. When you compile a word with "[COMPILE] foo",
|
||||||
|
its straightforward: It writes down to HERE wither the address of the word or
|
||||||
|
a number literal.
|
||||||
|
|
||||||
|
When you *meta* compile, it's a bit more mind blowing. It fetches the address
|
||||||
|
of the word specified by the caller, then writes that number as a literal,
|
||||||
|
followed by a reference to ",".
|
||||||
|
|
||||||
|
Example: ": foo [COMPILE] bar;" is the equivalent of ": foo bar ;" if bar is
|
||||||
|
not an immediate. However, ": foo COMPILE bar ;" is the equivalent of
|
||||||
|
": foo ['] bar , ;". Got it?
|
||||||
|
|
||||||
|
Meta-compile only works with real words, not number literals.
|
||||||
|
|
||||||
*** Flow ***
|
*** Flow ***
|
||||||
Note about flow words: flow words can only be used in definitions. In the
|
Note about flow words: flow words can only be used in definitions. In the
|
||||||
INTERPRET loop, they don't have the desired effect because each word from the
|
INTERPRET loop, they don't have the desired effect because each word from the
|
||||||
@ -132,6 +156,32 @@ SLEN a -- n Push length of str at a.
|
|||||||
|
|
||||||
*** I/O ***
|
*** I/O ***
|
||||||
|
|
||||||
|
A little word about inputs. There are two kind of inputs: direct and buffered.
|
||||||
|
As a general rule, we read line in a buffer, then feed words in it to the
|
||||||
|
interpreter. That's what "WORD" does. If it's at the End Of Line, it blocks and
|
||||||
|
wait until another line is entered.
|
||||||
|
|
||||||
|
KEY input, however, is direct. Regardless of the input buffer's state, KEY will
|
||||||
|
return the next typed key.
|
||||||
|
|
||||||
|
PARSING AND BOOTSTRAP: Parsing number literal is a very "core" activity of
|
||||||
|
Forth, and therefore generally seen as having to be implemented in native code.
|
||||||
|
However, Collapse OS' Forth supports many kinds of literals: decimal, hex, char,
|
||||||
|
binary. This incurs a significant complexity penalty.
|
||||||
|
|
||||||
|
What if we could implement those parsing routines in Forth? "But it's a core
|
||||||
|
routine!" you say. Yes, but here's the deal: at its native core, only decimal
|
||||||
|
parsing is supported. It lives in the "(parsed)" word. The interpreter's main
|
||||||
|
loop is initially set to simply call that word.
|
||||||
|
|
||||||
|
However, in core.fs, "(parsex)", "(parsec)" and "(parseb)" are implemented, in
|
||||||
|
Forth, then "(parse)", which goes through them all is defined. Then, "(parsef)",
|
||||||
|
which is the variable in which the interpreter's word pointer is set, is
|
||||||
|
updated to that new "(parse)" word.
|
||||||
|
|
||||||
|
This way, we have a full-featured (and extensible) parsing with a tiny native
|
||||||
|
core.
|
||||||
|
|
||||||
(parse) a -- n Parses string at a as a number and push the result
|
(parse) a -- n Parses string at a as a number and push the result
|
||||||
in n as well as whether parsing was a success in f
|
in n as well as whether parsing was a success in f
|
||||||
(false = failure, true = success)
|
(false = failure, true = success)
|
||||||
@ -143,15 +193,11 @@ SLEN a -- n Push length of str at a.
|
|||||||
number parsing. By default, (parse).
|
number parsing. By default, (parse).
|
||||||
(print) a -- Print string at addr a.
|
(print) a -- Print string at addr a.
|
||||||
. n -- Print n in its decimal form
|
. n -- Print n in its decimal form
|
||||||
.x n -- Print n's LSB in hex form. Always 2 characters.
|
.X n -- Print n in its hexadecimal form. In hex, numbers
|
||||||
.X n -- Print n in hex form. Always 4 characters. Numbers
|
|
||||||
are never considered negative. "-1 .X" --> ffff
|
|
||||||
." xxx" -- *I* Compiles string literal xxx followed by a call
|
." xxx" -- *I* Compiles string literal xxx followed by a call
|
||||||
to (print)
|
to (print)
|
||||||
|
are never considered negative. "-1 .X -> ffff"
|
||||||
C< -- c Read one char from buffered input.
|
C< -- c Read one char from buffered input.
|
||||||
DUMP n a -- Prints n bytes at addr a in a hexdump format.
|
|
||||||
Prints in chunks of 8 bytes. Doesn't do partial
|
|
||||||
lines. Output is designed to fit in 32 columns.
|
|
||||||
EMIT c -- Spit char c to output stream
|
EMIT c -- Spit char c to output stream
|
||||||
IN> -- a Address of variable containing current pos in input
|
IN> -- a Address of variable containing current pos in input
|
||||||
buffer.
|
buffer.
|
70
forth/fmt.fs
70
forth/fmt.fs
@ -1,6 +1,7 @@
|
|||||||
( requires core, parse )
|
( requires core, parse )
|
||||||
|
|
||||||
: _
|
( TODO FORGET this word )
|
||||||
|
: PUSHDGTS
|
||||||
999 SWAP ( stop indicator )
|
999 SWAP ( stop indicator )
|
||||||
DUP 0 = IF '0' EXIT THEN ( 0 is a special case )
|
DUP 0 = IF '0' EXIT THEN ( 0 is a special case )
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -15,7 +16,7 @@
|
|||||||
( that "0 1 -" thing is because we don't parse negative
|
( that "0 1 -" thing is because we don't parse negative
|
||||||
number correctly yet. )
|
number correctly yet. )
|
||||||
DUP 0 < IF '-' EMIT 0 1 - * THEN
|
DUP 0 < IF '-' EMIT 0 1 - * THEN
|
||||||
_
|
PUSHDGTS
|
||||||
BEGIN
|
BEGIN
|
||||||
DUP '9' > IF DROP EXIT THEN ( stop indicator, we're done )
|
DUP '9' > IF DROP EXIT THEN ( stop indicator, we're done )
|
||||||
EMIT
|
EMIT
|
||||||
@ -24,53 +25,24 @@
|
|||||||
|
|
||||||
: ? @ . ;
|
: ? @ . ;
|
||||||
|
|
||||||
: _
|
: PUSHDGTS
|
||||||
DUP 9 > IF 10 - 'a' +
|
999 SWAP ( stop indicator )
|
||||||
ELSE '0' + THEN
|
DUP 0 = IF '0' EXIT THEN ( 0 is a special case )
|
||||||
;
|
|
||||||
|
|
||||||
( For hex display, there are no negatives )
|
|
||||||
|
|
||||||
: .x
|
|
||||||
256 MOD ( ensure < 0x100 )
|
|
||||||
16 /MOD ( l h )
|
|
||||||
_ EMIT ( l )
|
|
||||||
_ EMIT
|
|
||||||
;
|
|
||||||
|
|
||||||
: .X
|
|
||||||
256 /MOD ( l h )
|
|
||||||
.x .x
|
|
||||||
;
|
|
||||||
|
|
||||||
( a -- a+8 )
|
|
||||||
: _
|
|
||||||
DUP ( save for 2nd loop )
|
|
||||||
':' EMIT DUP .x SPC
|
|
||||||
4 0 DO
|
|
||||||
DUP @
|
|
||||||
256 /MOD SWAP
|
|
||||||
.x .x
|
|
||||||
SPC
|
|
||||||
2 +
|
|
||||||
LOOP
|
|
||||||
DROP
|
|
||||||
8 0 DO
|
|
||||||
DUP C@
|
|
||||||
DUP <>{ 0x20 &< 0x7e |> <>}
|
|
||||||
IF DROP '.' THEN
|
|
||||||
EMIT
|
|
||||||
1 +
|
|
||||||
LOOP
|
|
||||||
LF
|
|
||||||
;
|
|
||||||
|
|
||||||
( n a -- )
|
|
||||||
: DUMP
|
|
||||||
LF
|
|
||||||
BEGIN
|
BEGIN
|
||||||
OVER 1 < IF DROP EXIT THEN
|
DUP 0 = IF DROP EXIT THEN
|
||||||
_
|
16 /MOD ( r q )
|
||||||
SWAP 8 - SWAP
|
SWAP ( r q )
|
||||||
|
DUP 9 > IF 10 - 'a' +
|
||||||
|
ELSE '0' + THEN ( q d )
|
||||||
|
SWAP ( d q )
|
||||||
|
AGAIN
|
||||||
|
;
|
||||||
|
|
||||||
|
: .X ( n -- )
|
||||||
|
( For hex display, there are no negatives )
|
||||||
|
PUSHDGTS
|
||||||
|
BEGIN
|
||||||
|
DUP 'f' > IF DROP EXIT THEN ( stop indicator, we're done )
|
||||||
|
EMIT
|
||||||
AGAIN
|
AGAIN
|
||||||
;
|
;
|
||||||
|
88
usage.txt
88
usage.txt
@ -1,88 +0,0 @@
|
|||||||
Collapse OS usage guide
|
|
||||||
|
|
||||||
This document is not meant to be an introduction to Forth, but to instruct the
|
|
||||||
user about the peculiarities of this Forth implemenation. Be sure to refer to
|
|
||||||
dictionary.txt for a word reference.
|
|
||||||
|
|
||||||
*** DOES>
|
|
||||||
|
|
||||||
Used inside a colon definition that itself uses CREATE, DOES> transforms that
|
|
||||||
newly created word into a "does cell", that is, a regular cell ( when called,
|
|
||||||
puts the cell's addr on PS), but right after that, it executes words that appear
|
|
||||||
after the DOES>.
|
|
||||||
|
|
||||||
"does cells" always allocate 4 bytes (2 for the cell, 2 for the DOES> link) and
|
|
||||||
there is no need for ALLOT in colon definition.
|
|
||||||
|
|
||||||
At compile time, colon definition stops processing words when reaching the
|
|
||||||
DOES>.
|
|
||||||
|
|
||||||
Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;"
|
|
||||||
|
|
||||||
*** Compilation vs meta-compilation
|
|
||||||
|
|
||||||
Compilation vs meta-compilation. When you compile a word with "[COMPILE] foo",
|
|
||||||
its straightforward: It writes down to HERE wither the address of the word or
|
|
||||||
a number literal.
|
|
||||||
|
|
||||||
When you *meta* compile, it's a bit more mind blowing. It fetches the address
|
|
||||||
of the word specified by the caller, then writes that number as a literal,
|
|
||||||
followed by a reference to ",".
|
|
||||||
|
|
||||||
Example: ": foo [COMPILE] bar;" is the equivalent of ": foo bar ;" if bar is
|
|
||||||
not an immediate. However, ": foo COMPILE bar ;" is the equivalent of
|
|
||||||
": foo ['] bar , ;". Got it?
|
|
||||||
|
|
||||||
Meta-compile only works with real words, not number literals.
|
|
||||||
|
|
||||||
*** I/O
|
|
||||||
|
|
||||||
A little word about inputs. There are two kind of inputs: direct and buffered.
|
|
||||||
As a general rule, we read line in a buffer, then feed words in it to the
|
|
||||||
interpreter. That's what "WORD" does. If it's at the End Of Line, it blocks and
|
|
||||||
wait until another line is entered.
|
|
||||||
|
|
||||||
KEY input, however, is direct. Regardless of the input buffer's state, KEY will
|
|
||||||
return the next typed key.
|
|
||||||
|
|
||||||
PARSING AND BOOTSTRAP: Parsing number literal is a very "core" activity of
|
|
||||||
Forth, and therefore generally seen as having to be implemented in native code.
|
|
||||||
However, Collapse OS' Forth supports many kinds of literals: decimal, hex, char,
|
|
||||||
binary. This incurs a significant complexity penalty.
|
|
||||||
|
|
||||||
What if we could implement those parsing routines in Forth? "But it's a core
|
|
||||||
routine!" you say. Yes, but here's the deal: at its native core, only decimal
|
|
||||||
parsing is supported. It lives in the "(parsed)" word. The interpreter's main
|
|
||||||
loop is initially set to simply call that word.
|
|
||||||
|
|
||||||
However, in core.fs, "(parsex)", "(parsec)" and "(parseb)" are implemented, in
|
|
||||||
Forth, then "(parse)", which goes through them all is defined. Then, "(parsef)",
|
|
||||||
which is the variable in which the interpreter's word pointer is set, is
|
|
||||||
updated to that new "(parse)" word.
|
|
||||||
|
|
||||||
This way, we have a full-featured (and extensible) parsing with a tiny native
|
|
||||||
core.
|
|
||||||
|
|
||||||
*** Chained comparisons
|
|
||||||
|
|
||||||
The unit "cmp.fs" contains words to facilitate chained comparisons with a single
|
|
||||||
reference number. This allows, for example, to easily express "a == b or a == c"
|
|
||||||
or "a > b and a < c".
|
|
||||||
|
|
||||||
The way those chained comparison words work is that, unlike single comparison
|
|
||||||
operators, they don't have a "n1 n2 -- f" signature, but rather a "n1 f n2 -- n1
|
|
||||||
f" signature. That is, each operator "carries over" the reference number in
|
|
||||||
addition to the latest flag.
|
|
||||||
|
|
||||||
You open a chain with "<>{" and you close a chain with "<>}". Then, in between
|
|
||||||
those words, you can chain operators. For example, to check whether A == B or A
|
|
||||||
== C, you would write:
|
|
||||||
|
|
||||||
A <>{ B &= C |= <>}
|
|
||||||
|
|
||||||
The first operator must be of the "&" type because the chain starts with its
|
|
||||||
flag to true. For example, "<>{ <>}" yields true.
|
|
||||||
|
|
||||||
To check whether A is in between B and C inclusively, you would write:
|
|
||||||
|
|
||||||
A <>{ B 1 - &> C 1 + &< <>}
|
|
Loading…
Reference in New Issue
Block a user