On Friday, June 12, 2020 at 12:44:15 AM UTC-7,
gwj....@gmail.com wrote:
> What's pathetic about you is that you can't comment on something
> without including insults. Luckily most people have killfiled you
> so your opinions are worthless. But they serve a purpose - newbies to c.l.f
> can quickly see what an obnoxious person you are
> and discount anything you say.
Well, I upgraded my <SWITCH to support <WHEN ... WHEN> that is
roughly comparable to Michael Gassanenko's WHEN in that it
allows a variable number of targ values for each action.
I still haven't looked at Michael Gassanenko's code in any depth.
Unlike Gerry Jackson, I write my own code --- I don't steal
other people's code and put my own copyright on it.
I'm not disparaging Michael Gassanenko's ability --- he is a
real Forth programmer --- I just don't steal anybody's code.
I also made my duplicate-value error-message more informative
(it tells the user what the duplicate value was, which is useful
if the duplicate occurs inside of a wrapper around CASE-OF
rather than when CASE-OF is used directly).
I improved my documentation slightly.
Here is the code (that I didn't steal from anybody):
-----------------------------------------------------------------
\ ******
\ ****** This is a <SWITCH control-structure like in C that generates a jump-table.
\ ******
\ You can build a colon word called XXX that switches on targ values. This is how:
\ <SWITCH
\ :NONAME drop ... ; targ CASE-OF
\ ...
\ :NONAME drop ... ; FAST-SWITCH> xxx ( selector -- )
\ The DROP in the :NONAME function gets rid of the selector-value. You might need this sometimes though.
\ There are some simple additions, that can be used instead of CASE-OF (they do CASE-OF internally):
\ :NONAME drop ... ; low-targ high-targ RANGE-OF \ matches all of the values from LOW-TARG to HIGH-TARG inclusive
\ :NONAME drop ... ; ." xxx" CHARS-OF \ matches all of the chars in the string
\ :NONAME drop ... ; <WHEN ... WHEN> \ matches all of the values between <WHEN and WHEN> (the .... means zero or more values)
\ Also, we have CASE: suggested by DXForth that can be used instead of CASE-OF .
\ The colon word XXX executes a table-entry corresponding to the targ value.
\ If there is no match, then the xt value prior to FAST-SWITCH> is executed as the default.
\ All of the table-entries and the default are given the selector value which they DROP if not needed.
\ It was HAA's suggestion that the selector value be provided --- I had dropped it internally previously.
\ The targ values don't have to be provided in any particular order --- they get sorted internally.
\ If a duplicate targ value is provided, CASE-OF will abort with an error message at compile-time.
\ This error message shows which line had the duplicate, and what the duplicate value is
\ (This helpful error message is an improvement over Michael Gassanenko's CHOOSE).
\ Note that my <WHEN ... WHEN> was written to provide comparable functionality to Michael Gassanenko's CHOOSE code.
\ This is pretty crude because, unlike in C, the table entries can't have common local variables.
\ I would have liked to use my rquotations, but they don't have an xt that is known at compile-time,
\ so it is not possible to build a jump-table at compile-time. They have a "continuation" that is only known at run-time.
\ My <SWITCH that I have here uses :NONAME for the table entries --- they can have common global variables.
\ FAST-SWITCH> uses the selector value as an index to do the table look up. This is very fast.
\ If the range is too large however, then FAST-SWITCH> will abort with an error message to save memory.
\ In this case, use SLOW-SWITCH> instead. This builds a smaller table and uses BSEARCH to look up the table-entry.
\ Set JT-LIMIT to the range that you want FAST-SWITCH> to support.
\ It is currently set at 2^16 so you can have jump-tables with up to 64K entries. These consume a lot of memory.
\ If memory usage is an issue, then set JT-LIMIT to a smaller value. Use SLOW-SWITCH> instead of FAST-SWITCH>.
\ If the jump-table is sparse, SLOW-SWITCH> might be faster because there is less data-cache thrashing.
\ <SWITCH is primarily provided for writing VM simulators.
\ JT-LIMIT is currently set at 2^16, so it supports simulating a micro-processor with 16-bit opcodes (such as the AVR).
\ SLOW-SWITCH> would be needed for a micro-processor with 32-bit opcodes (such as the ARM or MIPS).
\ Some micro-processors (or byte-code VMs) have 8-bit opcodes, but also have post-bytes on some of the opcodes.
\ These variable-sized opcodes could be done with nested FAST-SWITCH> constructs.
list
w field .xt
w field .targ
constant jt
: init-jt ( xt targ node -- node )
init-list >r
r@ .targ !
r@ .xt !
r> ;
: new-jt ( xt targ -- node )
jt alloc
init-jt ;
: kill-jt ( head -- )
each[ dealloc ]each ;
: show-jt ( head -- )
each[ cr .targ @ . ]each ;
: jt> ( new-node node -- new-node flag ) \ used by INSERT-ORDERED to build an ascending list without duplicates
.targ @ over .targ @
2dup = if
cr ." targ value: " dup . dup h. dup b. dup 128 < if dup 32 > if ." char: " dup emit then then
cr true abort" *** <SWITCH structures not allowed to have duplicate targ values ***"
then
> ;
variable when-marker \ used by <WHEN values... WHEN> to determine how many values there are on the data-stack
: <switch ( -- head )
false when-marker !
nil ;
: case-of ( head xt targ -- new-head ) \ provide a targ value
new-jt \ -- head node
['] jt> swap insert-ordered drop ;
: range-of { head xt lo hi -- new-head } \ provide a range from LO to HI inclusive
head
hi 1+ lo do
xt I case-of
loop ;
: chars-of { head xt adr cnt -- new-head } \ provide a string containing targ chars
head
adr cnt + adr do
xt I c@ case-of
loop ;
: <when ( head xt -- head xt ) \ saves the depth after HEAD XT in WHEN-MARKER
when-marker @ abort" *** <WHEN can't be nested ***"
depth when-marker ! ;
: when> ( head xt values... )
when-marker @ 0= abort" *** <WHEN is needed prior to the values prior to the WHEN> ***"
depth when-marker @ - { total | cnt xt -- }
total 0< abort" *** WHEN> given an invalid marker ***"
total to cnt begin cnt while >r -1 +to cnt repeat \ -- head xt \return: -- values...
to xt \ -- head \return: -- values...
total to cnt begin cnt while xt r> case-of -1 +to cnt repeat \ -- new-head \return: --
false when-marker ! ; \ a <WHEN is needed to set WHEN-MARKER for the next WHEN>
: digit-of ( head xt -- new-head )
[char] 0 [char] 9 range-of ;
: lower-of ( head xt -- new-head )
[char] a [char] z range-of ;
: upper-of ( head xt -- new-head )
[char] A [char] Z range-of ;
: alpha-of { head xt -- new-head }
head
xt lower-of xt upper-of ;
: punctuation-of ( head xt -- new-head )
s| .,!?'";:[]()@#$%&| chars-of ;
: blank-of ( head xt -- new-head )
0 32 range-of ;
1 16 lshift value jt-limit \ should be at least 256 so we can support byte-code simulators
\ JT-LIMIT is the index that is too big for the jump-table. This can be any reasonable size.
\ The jump-table size is limited so the programmer doesn't accidentally build a jump-table consuming megabytes.
\ I set it at 2^16 to support simulating a micro-processor with 16-bit opcodes.
: fast-switch> { head default | adr offset size -- } \ stream: name
align here to adr
head .targ @ to offset
head tail .targ @ offset - to size
size jt-limit u> abort" *** FAST-SWITCH> has too large of a range. Use SLOW-SWITCH> instead. ***"
offset head each[ >r \ -- targ
begin r@ .targ @ over <> while default , 1+ repeat
r> .xt @ , 1+ ]each drop
: ( selector -- )
dup, offset lit, -, \ -- selector index
dup, size lit, u>, if, drop, default lit, execute, end,
w lit, *, adr lit, +, @, execute, ;,
head kill-jt ;
: slow-search ( array limit target -- element|false ) \ hard-coded for use by SLOW-SWITCH>
>r \ -- array limit \ return: -- target
begin dup while
dup 1 rshift \ -- array limit mid
dup d * fourth + \ -- array limit mid mid-element
r@ over @ = if nip nip nip rdrop exit then \ if found, return MID-ELEMENT
r@ over @ < if \ search left side
drop nip \ -- array mid \ MID is the new limit
else
d + >r \ -- array limit mid \ return: -- target new-array \ NEW-ARRAY is one element above middle element
1+ - \ -- array new-limit \ the 1+ is so we don't include the middle element
nip r> swap \ -- new-array new-limit \ MID-ELEMENT is the new ARRAY
then
repeat \ LIMIT is zero, so it can be used as a FALSE flag
nip rdrop ; \ -- false
\ SLOW-SEARCH assumes that the array element is D in size (two cells),
\ and the first cell is the integer that we are comparing against.
: slow-switch> { head default | adr size -- } \ stream: name
dalign here to adr \ use DALIGN so the element SLOW-SEARCH finds will be in the same data-cache line
head length to size
head each[ dup .targ @ , .xt @ , ]each
: ( selector -- )
adr lit, size lit, rover, postpone slow-search
dup, if, w lit, +, @, execute, end,
drop, default lit, execute, ;,
head kill-jt ;
\ CASE: and ;; were suggested by DXForth on comp.lang.forth to improve readability.
\ This works well when there is a list of integers, similar to CASE END-CASE in ANS-Forth.
get-current synonym case: :noname \ head targ -- head targ
: ;; ( head targ -- new-head ) \ this concludes the CASE: function (rather than ; as used in :NONAME usually)
postpone ; \ -- head targ xt
swap case-of ;
immediate
\ Note that the default should still be :NONAME and end with ; as usual.
-----------------------------------------------------------------