• R/O
  • HTTP
  • SSH
  • HTTPS

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Functions for working with the idealized calendar of Planet Xhilr


File Info

Rev. c9ca731a29c3838146d1e7e85626e1273ae7ca7f
Size 39,257 bytes
Time 2017-06-17 10:35:04
Author Joel Matthew Rees
Log Message

UD/MOD double integer division in M6800 assembler within figForth.
The assembler I use to assemble it is here:
https://sourceforge.net/p/asm68c/wiki/Home/
and it can be run on Joe H Allen's exorsim v. 1.1.
Surprisingly, the High-level Forth version is only around twice as slow as the assembler-level version (because it only uses right-shifts).

Content

( Forth code for calculating idealized lengths of months )
( relative to skip years in the world of )
( Bobbie, Karel, Dan, and Kristi, Sociology 500, a Novel. )

( by Ted Turpin, of the Union of Independent States, Xhilr )
( Earth Copyright 2017, Joel Matthew Rees )

( Permission granted to use for personal entertainment only. )

( -- If you need it for other purposes, rewriting it yourself is not that hard, )
( and the result will be guaranteed to satisfy your needs much more effectively. )

( See these chapters of Sociology 500, a Novel, on line: )
( <http://joel-rees-economics.blogspot.com/2017/03/soc500-03-08-calendar-math.html> )
( <http://joel-rees-economics.blogspot.jp/2017/04/soc500-03-09-calculating-months-skip-years.html> )
( <http://joel-rees-economics.blogspot.com/2017/04/soc500-03-10-computers.html> )

( Novel table of contents and preface here: )
( <http://joel-rees-economics.blogspot.com/2017/01/soc500-00-00-toc.html>. )

( You can save it as something like "econmonths.fs". ) 

( In gforth and most modern or emulated environments, )
( just paste it into the terminal of a running Forth session. )

( Run it with )

( 7 SHOWIDEALMONTHS )

(  for seven years, etc. )

( gforth can be found in the repositories at )
( <https://www.gnu.org/software/gforth/>. )

( It can also be obtained as a package from most modern OS distributions )
( and in many applications stores -- Android, yes, iOS, not yet for a while. )
( Or, for MSWindows, you can get it through Cygwin: <https://www.cygwin.com/>. )

( HTML documentation can be found on the web at )
( <http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/> )
( which includes a tutorial for experienced programmers. )

( An easier tutorial for Forth can be found at )
( <https://www.forth.com/starting-forth/>. )

( There is a newsgroup: comp.lang.forth, )
( which can be accessed from the web via, for example, Google newsgroups. )

( Joel Matthew Rees's own implementation of Forth can be found via )
( <http://bif-c.sourceforge.net/>, )
( but if you want to play with that, you'll have to compile it yourself. )
( Look in the wiki at <https://sourceforge.net/p/bif-c/wiki/Home/> for help. )

( Many other Forths should also work. )

( If you don't like Forth's postfix syntax, you might try bc, )
( which is an ancient calculator found in many modern OSses and Cygwin. )
( The bc source is here: <https://osdn.net/users/reiisi/pastebin/4988>. )
( This file is here: <https://osdn.net/users/reiisi/pastebin/4990>. )
 

( Uses integer math throughout. )
( Forth expression syntax is mostly postfix. )
( Only the definition syntax is prefix or infix. )
( I've added some comments with equivalent infix expressions )
( to help those unfamiliar with Forth. )


( Using baroque identifiers for ancient Forths. )
( fig-Forth used first three character + length significance in symbol tables. )

( And I should do this all in hexadecimal, to get a more accurate flavor. )


( INVERT, UM*, UM/MOD, S>D, 2DUP, and D- are already there in most modern Forths. )
( These definitions are only for ancient Forths, without the full set loaded, )
( especially pre-1983 fig and bif-c. )
( Un-comment them if you see errors like )
( UM* ? err # 0 )
( from PRMONTH or thereabouts. )

( : INVERT NOT ; ( n1 --- n2 : Bit invert is in some ancient Forths as NOT. )
: INVERT -1 XOR ; ( n1 --- n2 : Bit invert is not found at all in some ancient Forths. )

: UM* U* ; ( u u --- ud : modern name for unsigned mixed multiply )

( So this is just sloppy renaming in a sloppy fashion: )
( unsigned division with modulo remainder )
: UM/MOD U/ ; ( uddividend udivisor --- uremainder uquotient : If this doesn't work try M/MOD DROP: )
( : UM/MOD M/MOD DROP ; ( uddividend udivisor --- uremainder uquotient )


: S>D S->D ; ( n --- d : Modern name for single-to-double. )
: NEGATE MINUS ; ( n --- -n : Modern name for numeric negation. )
: DNEGATE DMINUS ; ( d --- -d : Modern name for double number negation. )

: DINVERT  INVERT SWAP INVERT SWAP ;   ( d1 --- d2 : Double bit invert. )

: 2DUP  OVER OVER ; ( d --- d d : DUPlicate top double cell on stack. )

: 2DROP DROP DROP ; ( d --- : DROP a double, for readability. )

: D-  DNEGATE D+ ; (  d1 d2 --- d : Difference of two doubles. )

: M* ( n n --- d : signed mixed multiply )
  2DUP XOR >R   ( The archetypical definition. )
  ABS SWAP ABS UM* 
  R> 0< IF DNEGATE THEN
; 

: 2SWAP ROT >R ROT R> ; ( d1 d2 --- d2 d1 : Swap top two doubles )

: 2ROT >R >R 2SWAP R> R> 2SWAP ; ( d0 d1 d2 --- d1 d2 d0 )

: 2OVER >R >R 2DUP R> R> 2SWAP ; ( d0 d1 --- d0 d1 d0 )

: D0= OR 0= ; ( d0 --- f : Test top double. )

: D0< SWAP DROP 0< ; ( d0 --- f : Test top double sign. )

: D= D- D0= ; ( d1 d2 --- f : Test the top two doubles for equality. )

: D< D- D0< ; ( d1 d2 --- f : Test the top two doubles for left being less. )

: 2>R SWAP >R >R ; ( Save a double away in true order, high word handy. )

: 2R> R> R> SWAP ; ( Bring back saved double. )

: 4DUP 2OVER 2OVER ; ( q --- q q : DUPlicate the top four cells on stack. )

( : QNEGATE ( q1 --- q2 : Negate top quadruple word. )
(  >R 0. R> 0 d- >r four times, or is it three with double at end? )
  

: DMAX ( d1 d2 --- d : Leave larger of top two. )
  4DUP D< IF 2SWAP 2DROP ELSE 2DROP THEN ;

: DMIN ( d1 d2 --- d : Leave smaller of top two. )
  4DUP D< IF 2DROP ELSE 2SWAP 2DROP THEN ;

( : R@ R ; ( Modern name for copy top of return stack. )

( Showing the above in infix won't help. )


( From here, we should load okay in modern Forths. ) 
( Most of the doubles handling will be faster at assembler level )
( -- even if all you have is the bit math. )


( JM/MOD is already there as M/MOD in some Forths: )
( : JM/MOD M/MOD ; ( uddividend udivisor -- uremainder udquotient ) 
: JM/MOD ( uddividend udivisor -- uremainder udquotient )
  >R 0 R> DUP >R UM/MOD R> SWAP >R UM/MOD R> ; 
( Tick ' has various semantics, even in different fig Forths. )
( This definition is safe, anyway. )

SP@ SP@ - ABS CONSTANT CELLWIDTH
( Infix won't help here, either, but I can try to explain: )
( CELLWIDTH = absolute-value-of difference-between SP-without-pointer and SP-with-pointer.  )

( Infix will be confusing here, too. )
: D@ ( adr --- d ) ( fetch a double )
  DUP CELLWIDTH + @ ( LS-CELL )
  SWAP @ ( MS-CELL )
;

( Infix will be confusing here, too. )
: D! ( d adr --- ) ( store a double )
  SWAP OVER ! ( MS-CELL )
  CELLWIDTH + ! ( MS-CELL )
;

( Left shifts can be done with addition. )
: SUM-2* DUP + ; ( u1 --- u2 : Double the top cell. Not fastest, not too slow. )
: SUM-D2* 2DUP D+ ; ( ud1 --- ud2 : Double the top double cell. Not fastest. )
: SLOW-Q2*  ( uq1 --- uq2 : Double the top double cell. Not fastest. )
  SUM-D2* >R OVER 0< IF 
    1 OR ( carry )
  THEN
  >R 
  SUM-D2*
  R> R> ;

: MY-BIT-COUNTER ( --- u ) ( Let's figure out how wide a CELL is. )
  0. 1. BEGIN
    SUM-D2* 2SWAP 1. D+ 2SWAP SP@ @
  UNTIL 2DROP DROP ;

MY-BIT-COUNTER CONSTANT CELLBITS 
CELLBITS CELLWIDTH /MOD CONSTANT BYTEBITS CONSTANT LEFTOVERBITS

( Semi-simulate local variables with the ability to fetch and store relative to top of stack. )

( Infix will be confusing here, too. )
: LC@ ( index -- sp[ix] ) ( 0 is top. PICK is available on many modern Forths. )
  1 + CELLWIDTH *  ( Skips over the index on stack. )
  SP@ + @  ( Assumes push-down stack. Will fail on push-up. )
;

( Infix will be confusing here, too. )
: LC! ( n index -- ) ( 0 is top. Just store. This is not ROLL. )
  2 + CELLWIDTH *  ( Skips over index and value on stack. )
  SP@ +  ( Assumes push-down stack. )
  ! ( *** Will fail in MISERABLE ways on push-up stacks! *** )
;

( Infix will be confusing here, too. )
: DLC@ ( index -- sp[ix] ) ( 0 is top. PICK is available on many modern Forths. )
  1 + CELLWIDTH *  ( Skips over the index on stack. )
  SP@ + D@  ( Assumes push-down stack. Will fail on push-up. )
;

( Infix will be confusing here, too. )
: DLC! ( d index -- ) ( 0 is top. Just store. This is not ROLL. )
  3 + CELLWIDTH *  ( Skips over index and double value on stack. )
  SP@ +  ( Assumes push-down stack. )
  D! ( *** Will fail in MISERABLE ways on push-up stacks! *** )
;

( This probably isn't really a good idea. Much better to just implement UMD* in assembler. )
( AL AH B --- QL QML QMH : unsigned double by unsigned single yielding three-cell unsigned )
: UDS* ( ud u --- uhq ) 
  DUP >R SWAP >R 
  ( AL B ) UM* 
  0 ( ready to sum into )
  R> R> 
  ( AH B ) UM*
  D+ 
;

( only for stealing by U3S/MOD and UQS/MOD ! )
( Should actually be in a private vocabulary, but old Forths and new Forths do those differently. )
: (HIDDEN3S/MOD) ( uq u --- uremainder uhquotient ) 
  DUP >R JM/MOD DROP ( AL AM R QMH ) ( B )
  R> SWAP >R ( AL AM R B ) ( QMH )
  DUP >R JM/MOD DROP ( AL R QML ) ( QMH B )
  R> SWAP >R ( AL R B ) ( QMH QML )
  JM/MOD DROP ( R QL ) ( QMH QML )
  R> R> ( R QL QML QMH )
;

( AL AML AMH B --- R QL QML QMH : unsigned 3-cell by unsigned single yielding 3-cell unsigned )
: U3S/MOD ( uhq u --- uremainder uhqquotient )
  0 SWAP ( AL AM AH 0 B ) ( Prime the chain. )
  (HIDDEN3S/MOD) 
;
( You want to know why this is okay. )
( For the intuitive approach, )
( consider the cell lower in order than the current cell )
( as on the other side of the effective fraction point. )
( Now consider that the lower order cell cannot be as large as 1 in the current cell. )
( The remainder cannot be as large as the divisor.
( Added together, they still cannot be as large as the divisor. )
( Therefore, once you prime the chain with a zero in the cell above, )
( the result cannot overfow into the higher order cell of the double dividend. )

( AL AML AMH AH B --- R QL QML QMH QH : unsigned 4-cell by unsigned single yielding 4-cell unsigned )
: UQS/MOD ( uqdividend udivisor --- uremainder uqquotient )
  0 SWAP ( AL AML AMH AH 0 B ) ( Prime the chain. )
  DUP >R JM/MOD DROP ( AL AML AMH R QH ) ( B )
  R> SWAP >R ( AL AML AMH R B ) ( QH )
  (HIDDEN3S/MOD)
(  DUP >R JM/MOD DROP -- AL AML R QMH ) ( QH B )
(  R> SWAP >R -- AL AML R B ) ( QH QMH )
(  DUP >R JM/MOD DROP -- AL R QML ) ( QH QMH B )
(  R> SWAP >R -- AL R B ) ( QH QMH QML )
(  JM/MOD DROP -- R QL ) ( QH QMH QML )
(  R> R> )
  R> ( R QL QML QMH )
;

( Given  AABB / EEFF == SSTT rem MMNN, )
( AA/EE == RR rem LL is an approximation, iff EE is not zero. )
( But EE == 00 => use AABB / FF. )
( For EE > 0, RR * EE + LL == AA, or [ RR + LL / EE ] * EE == AA )
( But LL / EE < 1, or [ LL / EE ] * 100 < 100 )
( { [ RR + LL / EE ] * EE } * 100 == AA * 100 } )
( { [ RR * EE ] * 100 + LL * 100 } < { AA * 100 + BB } )
( Thus, { RR * EE00 + LL00 } < AABB )
( Now, BB < 100, so )
( { [ RR * EE + 1 ] * 100 + LL * 100 } > { AA * 100 + BB } )
( or AABB < { [ RR + 1 ] * EE00 + LL00 }
( This gives us some confidence that )
( { [ RR - 1 ] * EEFF } <= AABB <= { [ RR + 1 ] * EEFF } )
( which means that a trial division should be easy to restore to the true result. )
( But we want to know for sure. )
( { RR * EE00 + LL00 } == AA00 )
( { RR * EE00 + LL00 + BB } == AABB )
( { RR * [ EE00 + FF ] + LL00 + BB } > AABB )
( { RR * EE00 + RR * FF + LL00 + BB } > AABB )
( { RR * EE00 + RR * FF + LL00 + BB } == { AABB + RR * FF } )
( { RR * EE00 + RR * FF + LL00 + BB } == { AA00 + BB + RR * FF } )
( Good thing we checked. )
( The closer BB -LL gets to FF, the harder it is to recover. )
( Pathological case, hexadecimal - 32FF / 1FF in byte columns: )
( 32FF / 100 == 32rFF, 32 * 1FF == 63CE. )
( 32FF / 1FF is almost 32FF / 200: 19r177. )
( In sixteen bits, not useful. )
( In eight bits, better, but still not very useful. )

( Starting from scratch: )
( A/B == CrD => C * B + D == A, D < B )
( B can be expressed in terms of the magnitude of the columns: )
( If B < Radix R, or the magnitude of the columns, use UQS/MOD. )
( If B == Magnitude of the columns, shift A. )
( B > Radix R,  B/R == PrL, )
( B == P*R + L, P == [B-L]/R )
( L == B - P*R )
( Then, )
( A == C * [ P*R + L] + D )
( A == CPR + CL + D )
( A / [P*R] == C + CL/[P*R] + D/[P*R] )
( A / [P*R] == C * [1 + L/[P*R]] + D/[P*R]  This goes in a circle. )
( A == C * [PR + L] + D )
( A / [PR + L] == C + D / [PR + L] , 0 <= D < B or 0 <= D < PR + L )
( C <= A / [PR + L] < C + 1 , which isn't all that useful, either. )
( But 0 <= L < R, so )
( A / {[P + 1] * R} < A / [PR + L] <= A / PR , which restates the above. )

( Asking at comp.lang.forth produced this suggestion from Andrew Haley: )
( http://surface.syr.edu/cgi/viewcontent.cgi?article=1162&context=eecs_techreports )
( And from Rudy Velthius -- also mentions divmnu.c ) 
( https://github.com/rvelthuis/BigNumbers )
( It pretty much agrees with what I'm seeing above. )
( Doing it in binary math is the right way for this. )


( AL AH BL BH --- QL QML QMH QH : unsigned double by unsigned double yielding unsigned quad )
: UMD* ( ud1 ud2 --- uq ) 
  ( AL ) 3 LC@ ( BL ) 2 LC@ UM* 0 ( QL QML QMH : low cells product, ready to sum into QML QMH )
  ( AH ) 5 LC@ ( BL ) 5 LC@ UM* >R 0 D+ ( inner product low int QML and carry )
  ( AL ) 6 LC@ ( BH ) 4 LC@ UM* >R 0 D+ ( again, QML complete. )
  0 ( zero to QH, ready to sum into QMH QH )
  R> 0 D+ R> 0 D+ ( QL QML QMH QH : inner product high into QMH and carry )
  ( AH ) 6 LC@ ( BH ) 5 LC@ UM* D+ ( Product complete, now store it. )
  3 LC! 3 LC! 3 LC! 3 LC! 
;


( 2/ and d2/ require words which have various names -- u/, etc., )
( and are very slow. )
( Just best to do in assembler, along with UD* and UQD/MOD . )

( Do it in assembler instead! Hundreds of times as slow!!!! )
: DIV-2/ ( u1 --- u2 : Halve the top cell. REALLY SLOW! )
  S>D 2 UM/MOD SWAP DROP ;

( Do it in assembler instead! Hundreds of times as slow!!!! )
: DIV-D2/ ( ud1 --- ud2 : Halve the top double cell. REALLY SLOW! )
  2 JM/MOD ROT DROP ;

( Scaling, to keep the steps time-bounded, )
( is going to leave me at the binary long division )
( unless I use tables. )
( Tables will not fit in a 16-bit address space. )
( And scaling requires shifts, )
( which are painfully slow if not defined low level. )
( Some dividends will overflow quotient, not valid for such cases. )
( Intended to be used for known products of two doubles. 
( AL AML AMH AH BL BH --- RL RH QL QH : unsigned quad by unsigned double yielding unsigned double )
: SLOW-UMD/MOD ( uqdividend uddivisor --- udremainder udquotient )
  DUP 0= IF
    DROP UQS/MOD 2DROP 0 ROT ROT ( Get divisor high word 0 easy case done quickly. )
  ELSE
    2ROT 2ROT ( Get the divisor out of the way, but accessible with pick. )
    CELLBITS SUM-2* 1+ >R   ( Count )
    0 >R   ( Force flag )
    BEGIN ( BL BH AL AML AMH AH ) ( [ count force ] )
      2DUP    ( high double of dividend : BL BH AL AML AMH AH AMH AH )
      6 DLC@ D< 0=   ( Greater or equal? : BL BH AL AML AMH AH f )
      R> OR    ( Force it? )
      IF   ( BL BH AL AML AMH AH ) ( [ count ] )
        4 DLC@ D- 1 ( Mark the subtraction. )
      ELSE
        0 ( Mark no subtraction. )
      THEN   ( BL BH AL AML AMH AH bit ) ( [ count ] )
      SWAP >R SWAP >R   ( Save top half of remainder and bury the subtraction flag. )
      ( BL BH AL AML bit ) ( [ count AH AMH ] )
      OVER >R   ( Remember the carry from bottom to top half -- AML. )
      ( BL BH AL AML bit ) ( [ count AH AMH AML ] )
      >R SUM-D2*   ( Save subtraction flag and shift the bottom half: AL AML. )
      ( BL BH sAL rsAML ) ( [ count AH AMH AML bit ] )
      SWAP    ( BL BH rsAML sAL ) ( [ count AH AMH AML bit ] )
      R> OR SWAP   ( Record the subtraction in emptied bit of remainder. )
      ( BL BH rsAL rsAML ) ( [ count AH AMH AML ] )
      R> 0< IF 1 ELSE 0 THEN ( Convert AML to bit to shift in to top half. )
      ( BL BH rsAL rsAML carry ) ( [ count AH AMH ] )
      R> R>   ( BL BH rsAL rsAML carry AMH AH ) ( [ count ] )
      R> 1 - DUP >R    ( Count down. )
      ( BL BH rsAL rsAML carry AMH AH newcount ) ( [ newcount ] )
      WHILE   ( BL BH rsAL rsAML carry AMH AH ) ( [ newcount ] )
        DUP 0< >R   ( Remember the high bit of the remainder, to force subtract. )
        ( BL BH rsAL rsAML carry AMH AH ) ( [ newcount newforce ] )
        SUM-D2*   ( BL BH rsAL rsAML carry sAMH rsAH ) ( [ newcount newforce ] )
        >R OR R>   ( Shift the remainder, with the bit from the low half. )
        ( BL BH rsAL rsAML rsAMH rsAH ) ( [ newcount newforce ] )
    REPEAT   ( BL BH rsAL rsAML rsAMH rsAH ) ( [ newcount newforce ] )
    ( BL BH rsAL rsAML carry AMH AH ) ( [ newcount ] )
    R> DROP   ( the count ) ( BL BH rsAL rsAML carry AMH AH )
    ROT DROP   ( BL BH QL QH RL RH )
    2ROT 2DROP   ( QL QH RL RH )
    2SWAP    ( RL RH QL QH )
  THEN
; 


( Make things easier to read. )
( Infix will be confusing here, too. )

: PRCH EMIT ;

: COMMA 44 PRCH ;
: COLON 58 PRCH ;
: POINT 46 PRCH ;
: LPAREN 40 PRCH ;
: RPAREN 41 PRCH ;
: VBAR 124 EMIT ;
: PLUS 43 EMIT ;
: DASH 45 EMIT ;
: STAR 42 EMIT ;
: ZERO 48 EMIT ;

( No trailing space. )
: PSNUM ( number -- )
  0 .R ;

: PSDNUM ( number -- )
  0 D.R ;

( Do it in integers! )

( Watch limits on 16 bit processors! )

7 CONSTANT SCYCLE ( years in short cycle )
( SCYCLE = 7 )

7 2 * CONSTANT SPMCYC ( short cycles in medium cycle )
( SPMCYC = 7 × 2 )
 
SCYCLE SPMCYC * CONSTANT MCYCLE ( years in medium cycle, should be 98 )
( MCYCLE = SCYCLE × SPMCYC )

7 7 * CONSTANT SPLCYC ( short cycles in single long cycle )
( SPLCYC = 7 × 7 )

SCYCLE SPLCYC * CONSTANT LCYCLE ( years in single long cycle, should be 343 )
( LCYCLE = SCYCLE × SPLCYC )

7 CONSTANT MP2LCYC ( medium cycles in double long cycle )
( MP2LCYC = 7 )
( MPLCYC would not be an integer: 3 1/2 )

MCYCLE MP2LCYC * CONSTANT 2LCYCLE ( years in double long cycle, should be 686 )
( 2LCYCLE = MCYCLE × MP2LCYC )

352 CONSTANT DPSKIPYEAR ( floor of days per year  )


5 CONSTANT RDSCYCLE ( remainder days in short cycle )

DPSKIPYEAR SCYCLE * RDSCYCLE + CONSTANT DPSCYCLE ( whole days per 7 year cycle )
( DPSCYCLE = DPSKIPYEAR × SCYCLE + RDSCYCLE )

( DPMCYCLE and DP2LCYCLE would overflow on 16-bit math CPUs. ) 
( No particular problem on 32 bit CPUs. Need DCONSTANT for 16-bit CPUs. )
( But we need the constants more than we need to puzzle out )
( the differences between CREATE DOES> and <BUILDS DOES>. )

1 CONSTANT EDMCYCLE ( whole days adjusted down in 98 year cycle )

RDSCYCLE SPMCYC * EDMCYCLE - CONSTANT RDMCYCLE ( remainder days in medium cycle )
( RDMCYCLE = RDSCYCLE × SPMCYC - EDMCYCLE )

( DPSCYCLE SPMCYC UM* EDMCYCLE 0 D- DCONSTANT DPMCYCLE : 34565, too large for signed 16 bit. )
( DPMCYCLE = DPSCYCLE × SPMCYC - EDMCYCLE )
( Fake DCONSTANT: )
: DPMCYCLE [ DPSCYCLE SPMCYC UM* EDMCYCLE 0 D- SWAP ] LITERAL LITERAL ; ( Fits in unsigned 16 bit. )

2 CONSTANT SD2LCYCLE ( whole days adjusted up in 686 year cycle )

RDMCYCLE MP2LCYC * SD2LCYCLE + CONSTANT RD2LCYCLE ( remainder days in double long cycle -- odd number )
( RD2LCYCLE = RDMCYCLE × MP2LCYC + SD2LCYCLE )
( RD2LCYCLE / 2LCYCLE is fractional part of year. )
( Ergo, length of year is DPSKIPYEAR + RD2LCYCLE / 2LCYCLE, )
( or 352 485/686 days. )

( D* is not defined, but, luckily, DPMCYCLE fits in unsigned 16 bit. )
( 100 years of 365.24 also fits in unsigned 16 bit, FWIW. )
( DPLCYCLE would not be an integer, leaves a half day over. )
( DPMCYCLE MP2LCYC S>D D* SD2LCYCLE 0 D+ DCONSTANT DP2LCYCLE : 241957 , too large for 16 bit. )
( DP2LCYCLE = DPMCYCLE × MP2LCYC + SD2LCYCLE )
( Fake DCONSTANT: )
: DP2LCYCLE [ DPMCYCLE ( 34565 ) DROP MP2LCYC UM* SD2LCYCLE 0 D+ SWAP ] LITERAL LITERAL ; 

12 CONSTANT MPYEAR ( months per year )

DPSKIPYEAR MPYEAR /MOD CONSTANT FDMONTH ( floor of days per month )
( FDMONTH = DPSKIPYEAR / MPYEAR )
CONSTANT FRMONTH ( floored minimum remainder days per month )
( FRMONTH = DPSKIPYEAR MOD MPYEAR )

2LCYCLE MPYEAR * CONSTANT MDENOMINATOR ( denominator of month fractional part )
( MDENOMINATOR = 2LCYCLE × MPYEAR  )

FRMONTH 2LCYCLE * RD2LCYCLE + CONSTANT MNUMERATOR ( numerator of month fractional part )
( MNUMERATOR  = FRMONTH × 2LCYCLE + RD2LCYCLE )
( Ergo, length of month is FDMONTH + MNUMERATOR / MDENOMINATOR, )
( or 29 3229/8232 days. )

MDENOMINATOR 2 / CONSTANT MROUNDFUDGE

( Infix will be confusing below here, as well. )
( Hopefully, the comments and explanations will provide enough clues. )

( Sum up the days of the months in a year. )
: SU1MONTH ( startfractional dstartdays -- endfractional denddays )
  FDMONTH S>D D+ ( Add the whole part. )
  ROT ( Make the fractional part available to work on. )
  MNUMERATOR + ( Add the fractional part. )
  DUP MDENOMINATOR < ( Have we got a whole day yet? )
  IF 
    ROT ROT ( No, restore stack order for next pass. )
  ELSE
    MDENOMINATOR - ( Take one whole day from the fractional part. )
    ROT ROT 1 S>D D+ ( Restore stack and add the day carried in. )
  THEN
;

: PRMONTH ( fractional ddays -- fractional ddays )
  SPACE 2DUP PSDNUM POINT ( whole days )
  2 LC@ 1000 UM* ( Fake three digits of decimal precision. )
  MROUNDFUDGE S>D D+ ( Round the bottom digit. )
  MDENOMINATOR UM/MOD ( Divide, or evaluate the fraction. )
  S>D <# # # # #> ( Formatting puts most significant digits in buffer first. )
  TYPE ( Fake decimal output. )
  DROP SPACE 
;

: SH1IDEALYEAR ( year ddaysmemory fractional ddays -- year ddaysmemory fractional ddays )
  CR
  MPYEAR 0 DO 
    5 LC@ PSNUM SPACE ( year )
    I PSNUM COLON SPACE
    SU1MONTH 
    2DUP 5 DLC@ D- ( difference in days )
    4 LC@ ( push difference to ceiling ) IF 1. D+ THEN 
    2DUP PSDNUM SPACE ( show theoretical days in month )
    5 DLC@ D+ ( sum of days: adjusted difference plus daysmemory ) 
    LPAREN 2DUP PSDNUM COMMA SPACE 
    3 DLC! ( update daysmemory )
    PRMONTH RPAREN CR
  LOOP
;

: SHOWIDEALMONTHS ( years -- )
  >R
  0 0. 0 0. ( year, ddaysmemory, fractional, ddays )
  R> 0 DO
    CR
    SH1IDEALYEAR
    5 LC@ 1+ 5 LC!
  LOOP
  2DROP DROP 2DROP DROP
;

  0 CONSTANT SKMONTH
  1 CONSTANT SK1SHORTCYC
  4 CONSTANT SK2SHORTCYC
 48 CONSTANT SKMEDIUMCYC
186 CONSTANT LPLONGCYC  ( Must be short1 or short2 within the seven year cycle. )
LCYCLE LPLONGCYC + CONSTANT LPLONGCYC2

( Since skipyears are the exception, )
( we test for skipyears instead of leapyears. )
( Calendar system starts with year 0, not year 1. )
( Would need to check and adjust if the calendar started with year )
: ISKIPYEAR ( year -- flag )
  DUP 0< IF
    NEGATE 2LCYCLE MOD 2LCYCLE SWAP -
  THEN
    DUP MCYCLE MOD SKMEDIUMCYC =
  IF DROP -1  ( One specified extra skip year in medium cycle. )
  ELSE
    DUP SCYCLE MOD DUP
    SK1SHORTCYC =
    SWAP SK2SHORTCYC = OR  ( Two specified skip years in short cycle, but ... )
    SWAP LCYCLE MOD LPLONGCYC = 0= AND ( not the specified exception in the long cycle. )
  THEN
;


( At this point, I hit a condundrum. )
( Modern "standard" Forths want variables without initial values, )
( but ancient, especially fig-Forths want initialized variables. )
( The lower-level <BUILDS DOES> for fig is only partially part of the modern standard. )
( And CREATE is initialized as a CONSTANT in the fig-Forth, )
( but has no initial characteristic code or value in modern standards. )
( So. )
( I can't fix this easily. )
( We give the ancient Forths a zero. )
( Modern Forths will leave the 0 given here on the stack. )
( Then the zero stays around forever on modern Forths, or until you drop it. )
0 VARIABLE DIMARRAY  ( Days In Months array )

CELLWIDTH NEGATE ALLOT ( Back up to store values. )

30 C, 
29 C,
30 C,
29 C,
29 C,
30 C,
29 C,
30 C,
29 C,
29 C,
30 C,
29 C,
 0 ,

( Accept one year year plus or minus, to help calendar on first and last month. )
: DIMONTH ( year month -- days )
  DUP 0< IF
    SWAP 1 - SWAP MPYEAR + 
  ELSE
    DUP MPYEAR < 0= IF
      SWAP 1 + SWAP MPYEAR -
    THEN
  THEN
  DUP 0 < 0=
  OVER MPYEAR < AND 0= 
  IF
    DROP DROP 0  ( Out of range. No days. )
  ELSE
    DUP DIMARRAY + C@  ( Get the basic days. )
    SWAP SKMONTH =  ( true if skip month )
    ROT ISKIPYEAR AND  ( true if skip month of skip year )
    1 AND - ( Subtrahend is 1 only if skip month of skip year. )
  THEN 
;
    
: SH1YEAR ( year ddaysmemory fractional ddays -- year ddaysmemory fractional ddays )
  CR
  MPYEAR 0 DO 
    5 LC@ PSNUM SPACE ( year )
    I PSNUM COLON SPACE
    SU1MONTH  ( ideal month )
    5 LC@ I DIMONTH  ( real month )
    DUP PSNUM SPACE ( show days in month )
    S>D 5 DLC@ D+ ( sum of days ) 
    LPAREN 2DUP PSDNUM COMMA SPACE 
    3 DLC! ( update )
    PRMONTH RPAREN CR
  LOOP
;

: SHOWMONTHS ( years --- )
  >R
  0 0. 0 0. ( year, ddaysmemory, fractional, ddays )
  R> 0 DO
    CR
    SH1YEAR
    5 LC@ 1+ 5 LC!
  LOOP
  2DROP DROP 2DROP DROP
;

: D, ( d --- ) ( Store a double into the dictionary. )
  SWAP , , ;

: DINY ( year --- days )
  ISKIPYEAR 0= 1 AND DPSKIPYEAR + ;

: DTYLONGLOOP ( years --- ddays ) ( Days in years. )
  0. ROT DUP IF 
    0 DO
      I DINY S>D D+
    LOOP
  ELSE
    DROP
  THEN
;

( Already did these the other way: )
( : DPMCYCLE [ MCYCLE DTYLONGLOOP SWAP ] LITERAL LITERAL ; ( 34565  )
( : DP2LCYCLE [ 2LCYCLE DTYLONGLOOP SWAP ] LITERAL LITERAL ; ( 241957 )

( Synthetic division is faster than general division. )
: DTYLONG ( ddays1 uyear1 --- ddays2 uyear2 ) ( Sum long cycle years. )
  BEGIN 
    2LCYCLE - DUP 0< 0= WHILE
      >R DP2LCYCLE D+ R>
  REPEAT
  2LCYCLE +
;

( Synthetic division is faster than general division. )
: DTYMEDIUM ( ddays1 uyear1 --- ddays2 uyear2 ) ( Sum medium cycle years with leaps. )
  DUP LPLONGCYC2 > IF
    >R 2. D+ R> 
  ELSE
    DUP LPLONGCYC > IF >R 1. D+ R> THEN
  THEN
  BEGIN 
    MCYCLE - DUP 0< 0= WHILE
      >R DPMCYCLE D+ R>
  REPEAT
  MCYCLE +
;

( Synthetic division is still faster : max 98 / 7 loops. )
: DTYSHORT ( ddays1 uyear1 --- ddays2 uyear2 ) ( Sum short cycle years with skip. )
  DUP SKMEDIUMCYC > IF
    >R 1. D- R> 
  THEN
  BEGIN 
    SCYCLE - DUP 0< 0= WHILE
      >R DPSCYCLE 0 D+ R>
  REPEAT
  SCYCLE +
;

( Synthetic division is faster than general division. )
( Anyway, this has only algorithmic meaning prior to the standard calendar. )
: DTY ( uyear --- ddays )
  0. ROT
  DTYLONG
  DTYMEDIUM
  DTYSHORT
  DTYLONGLOOP
  D+
; 

( Saturates on month > MPYEAR. Generally use to month MPYEAR - 1. )
: DTM ( uyear umonth --- days ) ( Just the days from the beginning of the year. )
  DUP IF
    0 SWAP 0 DO
      OVER I DIMONTH +
    LOOP
  THEN
  SWAP DROP
;


( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE CALENDAR-WIDTH
80  CALENDAR-WIDTH !
( But we won't use this because we don't have real strings. )
( Okay, we'll use it anyway. )

( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE DAYCOUNT
0 DAYCOUNT ! 0 , ( Double variable, initialize cleared. )


( Modern Forths will leave the initialization 0 behind. )
6 CONSTANT WKDAYOFFSET ( Weekday corresponding to day zero of year zero. )
0 VARIABLE 1STDAYOFWEEK ( Weekday corresponding to first day of week. )
0 1STDAYOFWEEK !

( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE DOWKSTATE ( Current day of week. )

7 CONSTANT DPWK ( Days per week. )


16 CONSTANT JIRPERDAY ( About 90 minutes. )
16 CONSTANT GOBUPERJIR ( About 5.6 minutes. )
16 CONSTANT BUNEIGHPERGOB ( About 21 seconds. )
16 CONSTANT MYOTPERBUNEIGH ( About 13 seconds. )


( For the cycles use scaled 485 / 686, keep scale in 16 bits. )
RD2LCYCLE 16 * CONSTANT NUCYCLE ( numerator: 7760 )
2LCYCLE 16 * CONSTANT DECYCLE ( denominator: 10976 )

( Their larger moon orbits their world in about twenty-eight and seven eighths days, )
( about twelve and one fifth long lunar months each year.)
28 CONSTANT SMPERIODINT ( Slow moon period integer part. )
7 DECYCLE 8 */ 41 + CONSTANT SMPERIODFRAC10976 ( Slow moon period fractional part. )
( Fake DCONSTANT: )
: SMPERIOD10976 [ SMPERIODINT DECYCLE UM* SMPERIODFRAC10976 0 D+ SWAP ] LITERAL LITERAL ; 
( 28 9645 / 10976 == 316973 / 10976 )

( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE SMSTATEINT ( Slow moon state integer part. )
0 SMSTATEINT ! 0 , ( Initialize cleared, make double variable. )
( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE SMSTATEFRAC10976 ( Fractional part. )
0 SMSTATEFRAC10976 ! 0 , ( Initialize cleared, make double variable. )

: SM16THPERIOD10976 [ SMPERIOD10976 8. D+ 16 JM/MOD ROT DROP SWAP ] LITERAL LITERAL ;
: SM32NDPERIOD10976 [ SMPERIOD10976 16. D+ 32 JM/MOD ROT DROP SWAP ] LITERAL LITERAL ;

( start + mt = 1/2, start + gt = 3/4 => t * { g - m } = 1/4 => t = 1 / 4 * { g - m } )
( g = 1 rot/day, m = 10976 / 316973 rev/day => t = 1 / { 4 * [ 316973 - 10976 ] / 316973 } )
( s + gt = 3/4 => s = 3/4 - t; s = 3/4 - 1 / { 4 * [ 316973 - 10976 ] / 316973 } ) 
( s + mt = 1/2 => s = 1/2 - mt; s = 1/2 - 10976 / [ 4 * { 316973 - 10976 } ] )
( s = [ 2 * 316973 - 3 * 10976 ] / [ 4 * { 316973 - 10976 } ] )
( s = 601018 / 1223988 )
: SMTARGET 
  [ 2. SMPERIOD10976 UMD* 2DROP 3 DECYCLE UM* D-
    SMPERIOD10976 UMD* ( Scale it by period and keep high double word. )
    4. SMPERIOD10976 DECYCLE 0 D- UMD* 2DROP 
    SLOW-UMD/MOD 2SWAP 2DROP SWAP
 ] LITERAL LITERAL ;
( Used SMTARGET like this, with SMOFFFRAC10976 set to 0.: )
( 34 3 STYCYCLES 5 DMADJUST SMSTATEFRAC10976 D@ SMTARGET 2SWAP  D- )
( SMPERIOD10976 D+ D. <enter> 311395 OK )
0 CONSTANT SMOFFINT ( Slow moon offset at year 0 day 0, integer part. )
: SMOFFFRAC10976  [ 311395. SWAP ] LITERAL LITERAL ; ( Fractional part. )

( Below was guessing wrong: )
(  [ SM32NDPERIOD10976 28 UDS* DROP )
(    SM32NDPERIOD10976 DIV-D2/ D+ 4 JM/MOD ROT DROP SWAP ] )

( Could pre-divide the period into 16ths but this is an output function, )
( can be a little slow. )
: SMSHOWPHASE ( --- ) ( --- ) ( Show the Slowmoon phase with no spacing. )
  SMSTATEFRAC10976 D@ SM32NDPERIOD10976 D+ 0. SM16THPERIOD10976 SLOW-UMD/MOD
  2SWAP 2DROP DROP DUP 16 < 0= IF 16 - THEN
  ." S:" HEX 0 .R DECIMAL
;

3 CONSTANT SPHASEWIDTH


( The smaller moon orbits their world in just under seven and one eighth days, )
( about forty-nine and a half lunar weeks a year )
7 CONSTANT FMPERIODINT ( Fast moon period integer part. )
1 DECYCLE 8 */ 9 -  CONSTANT FMPERIODFRAC10976 ( Fast moon period fractional part. )
( Fake DCONSTANT: )
: FMPERIOD10976 [ FMPERIODINT DECYCLE UM* FMPERIODFRAC10976 0 D+ SWAP ] LITERAL LITERAL ; 
( 7 1364 / 10976 == 78196 / 10976 )

( start + mt = 1/2, start + gt = 3/4 => t * { g - m } = 1/4 => t = 1 / 4 * { g - m } )
( g = 1 rot/day, m = -10976 / 78196 rev/day => t = 1 / { 4 * [ 78196 + 10976 ] / 78196 } )
( s + gt = 3/4 => s = 3/4 - t; s = 3/4 - 1 / { 4 * [ 78196 + 10976 ] / 78196 } ) 
( s + mt = 1/2 => s = 1/2 - mt; s = 1/2 + 10976 / [ 4 * { 78196 + 10976 } ] )
( s = [ 2 * 78196 + 3 * 10976 ] / [ 4 * { 78196 + 10976 } ] )
( s = 189318 / 356684 )
: FMTARGET 
  [ 2. FMPERIOD10976 UMD* 2DROP 3 DECYCLE UM* D+
    FMPERIOD10976 UMD* ( Scale it by period and keep high double word. )
    4. FMPERIOD10976 DECYCLE 0 D+ UMD* 2DROP 
    SLOW-UMD/MOD 2SWAP 2DROP SWAP
 ] LITERAL LITERAL ;
( Used FMTARGET like this, with FMOFFFRAC10976 set to 0.: )
( 34 3 STYCYCLES 5 DMADJUST FMSTATEFRAC10976 D@ FMTARGET 2SWAP  D- )
( D. <enter> 4287 OK )
0 CONSTANT FMOFFINT ( Fast moon offset at year 0 day 0, integer part. )
: FMOFFFRAC10976 [ 4287. SWAP ] LITERAL LITERAL ; ( Fractional part. )

( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE FMSTATEINT ( Fast moon state integer part. )
0 FMSTATEINT ! 0 , ( Initialize cleared, make double variable. )
( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE FMSTATEFRAC10976 ( Fractional part. )
0 FMSTATEFRAC10976 ! 0 , ( Initialize cleared, make double variable. )

: FM16THPERIOD10976 [ FMPERIOD10976 8. D+ 16 JM/MOD ROT DROP SWAP ] LITERAL LITERAL ;
: FM32NDPERIOD10976 [ FMPERIOD10976 16. D+ 32 JM/MOD ROT DROP SWAP ] LITERAL LITERAL ;

( Could pre-divide the period into 16ths but this is an output function, )
( can be a little slow. )
: FMSHOWPHASE ( --- ) ( Show the Fastmoon phase with no spacing. )
  FMSTATEFRAC10976 D@ FM32NDPERIOD10976 D+ 0. FM16THPERIOD10976 SLOW-UMD/MOD
  2SWAP 2DROP DROP 
  JIRPERDAY SWAP - ( Retrograde. )
  DUP 16 < 0= IF 16 - THEN
  ." F:" HEX 0 .R DECIMAL
;

3 CONSTANT FPHASEWIDTH


( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE CYEAR 0 CYEAR !
( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE CMONTH 0 CMONTH !
( Modern Forths will leave the initialization 0 behind. )
0 VARIABLE CDATE 0 CDATE !

( Start the weekday counter for the year and month, remember the days. )
( Intended to be called from STYCYCLES. Other use will leave things out of sync. )
: WKSTCYCLES ( uyear umonth --- ) 
  2DUP
  CMONTH !
  CYEAR !
  0 CDATE !
  OVER DTY 
  2SWAP DTM 0 D+
  2DUP DAYCOUNT D!
  WKDAYOFFSET 0 D- DPWK JM/MOD 2DROP DOWKSTATE !
;

( Leaves things out of sync if not called by DADJUST. )
: BKMONTH ( --- )
  CMONTH @ 1 - DUP 0< IF
    CYEAR @ 1 - CYEAR !
    MPYEAR +
  THEN
  CMONTH !
;

( Leaves things out of sync if not called by DADJUST. )
: UPMONTH ( --- )
  CMONTH @ 1+ 
  DUP MPYEAR < 0= IF
    MPYEAR -
  THEN
  CMONTH !
;

( Adjusting by more than DIMONTH will leave CMONTH and maybe CYEAR incorrect. )
( Negative days will have previous month's DIMONTH as limit. )
( Leaves things out of sync if not called by DADJUST. )
: DTADJUST ( days --- )
  CDATE @ + 
  DUP 0< IF
    BKMONTH ( Previous month's DIMONTH. )
    CYEAR @ CMONTH @ DIMONTH +
  ELSE
    CYEAR @ CMONTH @ DIMONTH 2DUP < 0= IF
      -
      UPMONTH
    ELSE
      DROP
    THEN
  THEN
  CDATE !
;

( Leaves things out of sync if not called by DADJUST. )
: WDADJUST ( days --- )  ( Adjust the day of the week. )
  DOWKSTATE @ +
  DUP 0< IF 
    NEGATE DPWK MOD DPWK SWAP - 
  ELSE
    DPWK MOD 
  THEN
  DOWKSTATE !
;

( Start the slowmoon cycle counter by the day count. )
( Intended to be called from STYCYCLES. Other use will leave things out of sync. )
: SLOMSTCYCLES ( ddays --- ) 
  DECYCLE S>D UMD* SMPERIOD10976 SLOW-UMD/MOD 
  2SWAP SMOFFFRAC10976 D+
  2DUP SMPERIOD10976 D< 0= IF
    SMPERIOD10976 D- 2SWAP 1. D+ 2SWAP
  THEN
  SMSTATEFRAC10976 D!
  SMOFFINT S>D D+ SMSTATEINT D!
;

( Add signed days to slow month state. days must be less than period. )
: SLOMADJ ( days --- )  
  DECYCLE M* 
  SMSTATEFRAC10976 D@ D+ 
  2DUP D0< IF
    SMSTATEINT D@ 1. D- SMSTATEINT D!
    SMPERIOD10976 D+ 
  ELSE
    2DUP SMPERIOD10976 D< 0= IF
      SMSTATEINT D@ 1. D+ SMSTATEINT D!
      SMPERIOD10976 D-
    THEN
  THEN 
  SMSTATEFRAC10976 D!
;

( Start the fastmoon cycle counter by the day count. )
( Intended to be called from STYCYCLES. Other use will leave things out of sync. )
: FASMSTCYCLES ( ddays --- ) 
  DECYCLE S>D UMD* FMPERIOD10976 SLOW-UMD/MOD 
  2SWAP FMOFFFRAC10976 D+
  2DUP FMPERIOD10976 D< 0= IF
    FMPERIOD10976 D- 2SWAP 1. D+ 2SWAP
  THEN
  FMSTATEFRAC10976 D!
  FMOFFINT S>D D+ FMSTATEINT D!
;

( Add signed days to fast month state. days must be less than period. )
: FASMADJ ( days --- )  
  DECYCLE M* 
  FMSTATEFRAC10976 D@ D+ 
  2DUP D0< IF
    FMSTATEINT D@ 1. D- FMSTATEINT D!
    FMPERIOD10976 D+ 
  ELSE
    2DUP FMPERIOD10976 D< 0= IF
      FMSTATEINT D@ 1. D+ FMSTATEINT D!
      FMPERIOD10976 D-
    THEN
  THEN 
  FMSTATEFRAC10976 D!
;

( Call from here to keep things in sync! )
: STYCYCLES ( year month --- ) ( Start the counters for the year. )
  WKSTCYCLES 
  DAYCOUNT D@ 2DUP SLOMSTCYCLES FASMSTCYCLES
;

( : TEST STYCYCLES DAYCOUNT D@ D. CR SMSTATEINT D@ D. CR SMSTATEFRAC10976 D@ D. CR )
( FMSTATEINT D@ D. CR FMSTATEFRAC10976 D@ D. CR ; )

( Adjusting by more than DIMONTH will leave CMONTH and maybe CYEAR incorrect. )
( Negative days will have previous month's DIMONTH as limit. )
( Call from here to keep DAYCOUNT, DOWKSTATE, CYEAR, CMONTH, and CDATE in sync. )
: DADJUST ( days --- ) 
  DUP S>D DAYCOUNT D@ D+ DAYCOUNT D!
  DUP WDADJUST DTADJUST
;

( Adjusting by more than DIMONTH will leave CMONTH and maybe CYEAR incorrect. )
( Negative days will have previous month's DIMONTH as limit. )
( Call from here to keep moon phases also in sync. )
: DMADJUST ( days --- )
  DUP DADJUST DUP SLOMADJ FASMADJ
;


( Ancient Forths do not have standard WORDs, )
( and that makes it hard to have portable arrays of strings for those Forths. )
: TPWDAY ( n --- ) ( TYPE the name of the day of the week, modulo. )
   DPWK MOD
   DUP 0 = IF ."  Sunday " ELSE ( Fake case format to line the strings up. )
   DUP 1 = IF ." Moonsday" ELSE
   DUP 2 = IF ." Aegisday" ELSE
   DUP 3 = IF ." Gefnday " ELSE
   DUP 4 = IF ." Freyday " ELSE
   DUP 5 = IF ." Tewesday" ELSE  ( DUP here allows final single DROP. )
              ." Vensday " 
   THEN
   THEN
   THEN
   THEN
   THEN
   THEN 
   DROP ;

8 CONSTANT DWIDTH

: TPMONTH ( n --- ) ( TYPE the name of the month. )
(   DUP 6 < IF * Use this if the compile stack overflows. )
     DUP  0 = IF ." Time-division" ELSE ( Fake case format to line the strings up. )
     DUP  1 = IF ."  Deep-winter " ELSE
     DUP  2 = IF ."    War-time  " ELSE
     DUP  3 = IF ."   Thaw-time  " ELSE
     DUP  4 = IF ."    Rebirth   " ELSE
     DUP  5 = IF ."  Brides-month" ELSE 
(       ." ???" )
(     THEN THEN THEN THEN THEN THEN )
(   ELSE )
     DUP  6 = IF ."   Imperious  " ELSE
     DUP  7 = IF ."  Senatorious " ELSE
     DUP  8 = IF ."  False-summer" ELSE
     DUP  9 = IF ."    Harvest   " ELSE
     DUP 10 = IF ."   Gratitude  " ELSE
     DUP 11 = IF ."  Winter-month" ELSE ( DUP here allows final single DROP. )
                 ."      ???     " 
     THEN
     THEN
     THEN
     THEN
     THEN
     THEN
 ( For 0 to 5: )
     THEN
     THEN
     THEN
     THEN
     THEN
     THEN 
(   THEN )
   DROP ;

13 CONSTANT MWIDTH

CALENDAR-WIDTH @ DPWK / 1 - CONSTANT DFIELD

: WLINELENGTH CALENDAR-WIDTH @ DPWK / DPWK * ;

: DASHES ( count --- ) ( EMIT a string of count DASHes. )
  DUP 0 > IF
    0 DO DASH LOOP 
  ELSE
    DROP
  THEN
;

: HLINE ( --- )
  PLUS 
  DPWK 0 DO 
    DFIELD DASHES PLUS
  LOOP
  CR
;

: SPLINE ( --- )
  VBAR 
  DPWK 0 DO 
    DFIELD SPACES VBAR
  LOOP
  CR
;

: PWKDAYS ( --- ) ( Adjust by 1STDAYOFWEEK. )
  VBAR
  DFIELD DWIDTH - 1 - 2 /MOD 
  SWAP OVER + 
  1STDAYOFWEEK @ DUP DPWK + SWAP 
  DO
    DUP SPACES 
    I TPWDAY 
    DUP SPACES OVER SPACES
    VBAR
  LOOP
  CR
  DROP DROP
;

: BOLD ( n1 n2 --- )
  = IF STAR ELSE SPACE THEN ;

: PDFIELD ( day1 day2 --- ) ( Print day2 in day field with emphasis if same as day1. )
  DFIELD 4 - 2 /MOD    ( day1 day2 rem half )
  DUP ROT +   ( day1 day2 half half+rem )
  SPACES >R   ( day1 day2 ) ( [ half ] )
  2DUP BOLD DUP 2 .R BOLD   ( --- ) ( [ half ] )
  R> SPACES 
  VBAR
;

( DPWK days from start, emphasize and reset day if matched for month. )
: DAYLINE ( month day --- month daydone ) 
  VBAR 
  DPWK 0 DO
    OVER CMONTH @ = IF DUP ELSE -1 THEN
    CDATE @ 
    PDFIELD
    1 DADJUST
  LOOP
  CR
;


: PHLINE ( --- )
  VBAR
  DPWK 0 DO
    SMSHOWPHASE
    DFIELD SPHASEWIDTH - FPHASEWIDTH - SPACES
    FMSHOWPHASE
    VBAR
    1 SLOMADJ 1 FASMADJ
  LOOP
  CR
;

: CALMONTH ( year month day --- )
  CR
  ROT ROT STYCYCLES
  CMONTH @ SWAP  ( Remember month and day. )
  WLINELENGTH MWIDTH - 2 - 2 / SPACES 
  CYEAR @ 4 .R SPACE
  CMONTH @ TPMONTH CR
  HLINE
  PWKDAYS
  HLINE
  DOWKSTATE @ 1STDAYOFWEEK @ - DUP 0< IF DPWK + THEN
  DUP   ( Count of days to back up. )
  IF
    NEGATE DMADJUST
  ELSE 
    DROP
  THEN
  BEGIN
    SPLINE
    DAYLINE
    SPLINE
    SPLINE
    PHLINE
    HLINE
    OVER CMONTH @ < UNTIL
  DROP DROP
;




( Lots -- 10? -- of 0s left behind on modern Forths. )