Add chained comparison support

This commit is contained in:
Virgil Dupras 2020-04-05 16:11:17 -04:00
parent 7b34268a9a
commit 974018831e
5 changed files with 125 additions and 56 deletions

View File

@ -1,6 +1,6 @@
TARGETS = runbin/runbin forth/forth
# Those Forth source files are in a particular order
FORTHSRCS = core.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs
FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs
FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%} forth/run.fs
OBJS = emul.o libz80/libz80.o
SLATEST = ../tools/slatest

30
forth/cmp.fs Normal file
View File

@ -0,0 +1,30 @@
( Words useful for complex comparison operations )
( n1 -- n1 true )
: <>{ 1 ;
( n1 f -- f )
: <>} SWAP DROP ;
( n1 f n2 -- n1 cmp )
: |CMP
SWAP IF DROP 1 EXIT THEN ( n1 true )
OVER SWAP ( n1 n1 n2 )
CMP
;
: &CMP
SWAP NOT IF DROP 0 EXIT THEN ( n1 false )
OVER SWAP ( n1 n1 n2 )
CMP
;
( All words below have this signature:
n1 f n2 -- n1 f )
: |= |CMP NOT ;
: &= &CMP NOT ;
: |< |CMP -1 = ;
: &< &CMP -1 = ;
: |> |CMP 1 = ;
: &> &CMP 1 = ;

View File

@ -1,21 +1,11 @@
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
(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
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
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
@ -54,20 +44,6 @@ IMMEDIATE -- Flag the latest defined word as immediate.
LITN n -- Write number n as a literal.
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 ***
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
@ -156,32 +132,6 @@ SLEN a -- n Push length of str at a.
*** 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
in n as well as whether parsing was a success in f
(false = failure, true = success)

View File

@ -57,13 +57,14 @@
DROP
8 0 DO
DUP C@
DUP 0x20 < IF DROP '.' THEN
DUP 0x7e > IF DROP '.' THEN
DUP <>{ 0x20 &< 0x7e |> <>}
IF DROP '.' THEN
EMIT
1 +
LOOP
LF
;
( n a -- )
: DUMP
LF

88
forth/usage.txt Normal file
View File

@ -0,0 +1,88 @@
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 + &< <>}