mirror of
https://github.com/hsoft/collapseos.git
synced 2024-12-24 14:28:06 +11:00
Add chained comparison support
This commit is contained in:
parent
7b34268a9a
commit
974018831e
@ -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
30
forth/cmp.fs
Normal 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 = ;
|
||||
|
@ -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)
|
||||
|
@ -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
88
forth/usage.txt
Normal 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 + &< <>}
|
Loading…
Reference in New Issue
Block a user