From 974018831ef48ad6951f0a36f882a267abe5e9be Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Sun, 5 Apr 2020 16:11:17 -0400 Subject: [PATCH] Add chained comparison support --- emul/Makefile | 2 +- forth/cmp.fs | 30 ++++++++++++++++++ forth/dictionary.txt | 56 ++------------------------------- forth/fmt.fs | 5 +-- forth/usage.txt | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 56 deletions(-) create mode 100644 forth/cmp.fs create mode 100644 forth/usage.txt diff --git a/emul/Makefile b/emul/Makefile index 44bb5b6..0871977 100644 --- a/emul/Makefile +++ b/emul/Makefile @@ -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 diff --git a/forth/cmp.fs b/forth/cmp.fs new file mode 100644 index 0000000..05ab6d1 --- /dev/null +++ b/forth/cmp.fs @@ -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 = ; + diff --git a/forth/dictionary.txt b/forth/dictionary.txt index 978e873..a282957 100644 --- a/forth/dictionary.txt +++ b/forth/dictionary.txt @@ -1,21 +1,11 @@ +Be sure to read "usage.txt" for a guide to Collapse OS' Forth. + +*** Glossary *** Stack notation: " -- ". 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) diff --git a/forth/fmt.fs b/forth/fmt.fs index 4055ce9..d1c8e62 100644 --- a/forth/fmt.fs +++ b/forth/fmt.fs @@ -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 diff --git a/forth/usage.txt b/forth/usage.txt new file mode 100644 index 0000000..6c76228 --- /dev/null +++ b/forth/usage.txt @@ -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 + &< <>}