\ primes_bbs.fs -- WIP
\ Sift files of 1,000,000 primes each from t5k.org into...
\ ...those congruent-for-Blum Blum Shub and those not...
\ ...creating a separate file for each C_**.hexp, N_**.hexp
\ Also read a single prime from out of a *.hexp file.
\ Version: 2026-07-04 Copyright Gan Uesli Starling
\ License: None. Free for all uses.

\ Load order matters
S" ~/FYBB/edit_me.fs" INCLUDED \ You need to have edited this.
S" defs.fs"       include.file \ Some general user definitions
S" kybd.fs"       include.file \ So can query user
S" dec_to_bin.fs" include.file
S" math.fs"       include.file
S" bbs.fs"        include.file

: throw.file ( flg c-addr c -- )
  ROT 0<> IF
    CR CR ." Oops! " TYPE CR
    ABORT
  ELSE
    2DROP
  THEN
;

CREATE path_dir 255 ALLOT ALIGN \ Directory
CREATE path_p   255 ALLOT ALIGN
CREATE path_c   255 ALLOT ALIGN
CREATE path_n   255 ALLOT ALIGN
CREATE hex_p    255 ALLOT ALIGN \ Prime read as HEX from *.hexp

: path.erase ( addr -- addr )
  255 0 DO
    DUP I + 0 SWAP c!
  LOOP
;

: path.load ( c-addr1 cnt1 c-addr2 -- ) \ String & Count
  path.erase
  2DUP c!
  1+ SWAP CMOVE
;

S" /home/aplonis/Downloads" path_dir path.load

0 VALUE fd_p      \ For primes from t5k.org
0 VALUE fd_c      \ For congruent primes
0 VALUE fd_n      \ For non-congruent primes
0 VALUE row_chars \ Width of output rows
0 VALUE c_tally   \ Count of congruent primes
0 VALUE n_tally   \ Count of non-congruent
0 VALUE out_#     \ Which millionth prime set

: open.file.p   ( -- ) path_p COUNT R/O OPEN-FILE   path_p COUNT S"  @ open.file.p"   cs.append throw.file TO fd_p ;   
: open.file.c   ( -- ) path_c COUNT R/W OPEN-FILE   path_c COUNT S"  @ open.file.c"   cs.append throw.file TO fd_c ;
: open.file.n   ( -- ) path_n COUNT R/w OPEN-FILE   path_n COUNT S"  @ open.file.n"   cs.append throw.file TO fd_n ;
: create.file.c ( -- ) path_c COUNT W/O CREATE-FILE path_c COUNT S"  @ create.file.c" cs.append throw.file TO fd_c ;
: create.file.n ( -- ) path_n COUNT W/O CREATE-FILE path_N COUNT S"  @ create.file.n" cs.append throw.file TO fd_n ;

: close.file.p ( -- ) fd_p CLOSE-FILE S" close.file.p" throw.file ;
: close.file.c ( -- ) fd_c CLOSE-FILE S" close.file.c" throw.file ;
: close.file.n ( -- ) fd_n CLOSE-FILE S" close.file.n" throw.file ;

\ Runs slower than the mouse-macro colleting input files...
\ ...so can clean up behind as it goes. Used only by cff.* words
: delete.file.p ( 0|1 -- )
  IF
    1500 MS \ Wait for file to close
    path_p COUNT DELETE-FILE S" DELETE-FILE at cff.sift.files " throw.file
  THEN
;

: file.paths.load ( -- ) 
  path_dir COUNT S" /primes" cs.append out_# 0 <# #S #>  cs.append S" .txt"  cs.append path_p path.load
  path_dir COUNT S" /C_"     cs.append out_# 0 <# # # #> cs.append S" .hexp" cs.append path_c path.load
  path_dir COUNT S" /N_"     cs.append out_# 0 <# # # #> cs.append S" .hexp" cs.append path_n path.load
;

\ NOTE 1 -- ALL WORDS NAMED t5k.* RELATE TO DOING THIS...
\ 1. Sifting files named primes**.txt for congruent vs non-congruent primes
\ 2. Writing out sifted primes into files named C_**.hexp and N_**.hexp
 
\ NOTE 2 -- ALL WORDS NAMED cn.* ARE FOR...
\ 1. Reading in a file produced by t5k words, either C_**.hexp or N_**.hexp
\ 2. Obtaining from it a single prime, counted from either bottom or top

\ Files from t5k.org are formated 125k rows of 8 cols
: t5k.limit.row.col ( u1 u2 -- u3 u4 )
  ABS 8 MOD SWAP            \ Cols of primes in file
  ABS 125000 MOD SWAP       \ Rows of primes in file
;

\ Files from t5k.org have a header. Blank lines too.
\ Read a full line of 8 primes onto PAD
: t5k.get.line ( -- )
  BEGIN
    PAD 81 fd_p READ-LINE                  ( u flg ior )
    S" t5k.get.line READ-LINE" throw.file  ( u flg )
    DROP 81 <>                             ( flg )       \ 81 + CR + LF = 83 chars
  WHILE
  REPEAT
;

\ Get Nth row of 8 primes from bottom of a t5k.org primes file.
\ Calculate from EOF to avoid random-char-count header.
: t5k.nth.line ( u -- u flg ) 
  125000 SWAP - 83 * S>D                  \ 1/8 M lines, 8 cols: chars = 83 (81 + CR + LF)
  fd_p FILE-SIZE                          \
  S" t5k.nth.line FILE-SIZE" throw.file
  SWAP D-                                 \ Wanted row end
  83 S>D D-                               \ Wanted row start
  fd_p REPOSITION-FILE
  S" t5k.nth.line REPOSITION-FILE" throw.file  
  t5k.get.line                  ( )    \ Line of primes on PAD
;

\ Select single prime from row of eight in PAD
: t5k.nth.column ( u -- u ) 
  10 * PAD + 10 
  string.to.integer
;

: t5k.get.prime ( u1 u2 -- u ) \ Row & Column -- Prime
  t5k.limit.row.col       ( u1' u2' )
  SWAP t5k.nth.line      ( u2' )     \ CR PAD 80 TYPE CR
  t5k.nth.column         ( u )       \ DUP . CR
;

: t5k.u.to.file ( u fd -- )
  >R
  HEX 0 <# row_chars 0 DO # LOOP #> DECIMAL     ( c-addr cnt )
  R> WRITE-LINE
  S" t5k.u.to.file WRITE-LINE" throw.file
;

\ Test for BBS congruency and choose output file.
: sift.congruent ( u -- u fd )
  rsa_p a.erase            ( u )     \ Clear array
  DUP 1 rsa_p a.u!         ( u )     \ Load one cell into array
  rsa_p bbs.congruent?     ( u 0|1 ) \ Is it BBS congruent? Ref bbs.fs
  IF                       ( u )
    c_tally 1+ TO c_tally  ( u )
    fd_c                   ( u fd )  \ Congruents into own file
  ELSE 
    n_tally 1+ TO n_tally  ( u )
    fd_n                   ( u fd )  \ Why waste the dross?
  THEN
;

\ Top and bottom rows of file prime50.txt
\ 961748941 961748947 961748951 961748969 961748987 961748993 961749023 961749037 
\ 982451501 982451549 982451567 982451579 982451581 982451609 982451629 982451653

: t5k.get.row ( u )                   \ u = Row
  8 0 DO                              \ I = Column
    DUP I t5k.get.prime  ( row u )    \ Get prime from t5k.org file
    sift.congruent       ( row u fd )
    t5k.u.to.file        ( row )
  LOOP                   ( row )
  DROP                   ( )
;

: t5k.get.rows ( highest lowest )
  DO                  
    I t5k.get.row
  LOOP
;

: t5k.first.row
  S" Width: " row_chars 0 <# #S #> cs.append
  S"  Tally: " \ Ex-post-facto manual entry actual tally
  cs.append
  2DUP 
  fd_c WRITE-LINE S" t5k.first.row primes_bbs.fs" throw.file
  fd_n WRITE-LINE S" t5k.first.row primes_bbs.fs" throw.file
  
;

\ How many HEX chars wide should output rows be?
: t5k.col.bytewidth? ( -- )
  0 7 t5k.get.prime        ( u )               \ Get last value from input
  HEX 0 <# #S #> DECIMAL ( c-array count ) \ Convert to HEX string
  NIP TO row_chars     ( )                 \ Store into value
  t5k.first.row
;

\ Read all 1/8 million rows, obtaining 8 primes for each
\ Test for Blum Blum Shug congruency, saving out to file
: t5k.sift.primes
  open.file.p
  create.file.c
  create.file.n
  t5k.col.bytewidth?
  125000 0 t5k.get.rows       \ 1/8 million rows of 8 primes each
  fd_c FLUSH-FILE S" t5k.sift.primes FLUSH-FILE" throw.file
  fd_n FLUSH-FILE S" t5k.sift.primes FLUSH-FILE" throw.file
  close.file.n
  close.file.c
  close.file.p
;

\ Sift N files of 1M primes from www.t5k.org
: t5k.sift.files ( u u ) \ Smallest & highest files to sift
  SWAP
  DO
    0 TO c_tally
    0 TO n_tally
    I TO out_#
    file.paths.load
    CR CR ." Input file :" path_p COUNT TYPE CR
    t5k.sift.primes
    CR ." C = " c_tally . 
    ."  N = " n_tally .
  LOOP
;

\ After having read header line into pad, parse out the Width...
\ ...which could be multi-digit for very large primes.
: cn.parse.width ( 22 -- c-addr c )                  \ >Width: 8 Tally: 499905< in PAD
  PAD SWAP next.space 1+  ( pad 22 7 )            \ >Width: 8 Tally: 499905< 22 7
  SWAP OVER -             ( pad 7 15 )            \ >8 Tally: 499905< 15
  -ROT + SWAP             ( pad+7 15 )            \ >8 Tally: 499905<
  2DUP next.space         ( pad+7 15 pad+7 15 1 )
  DUP 1+ >us NIP          ( pad+7 15 pad+7 1 )    \ >8 Tally: 499905< >8<
  string.to.integer       ( pad+7 15 8 )          \ >8 Tally: 499905< 8
  TO row_chars            ( pad+7 15 )            \ >8 Tally: 499905<
  SWAP us@ +              ( 15 pad+9 )
  SWAP us> -              ( pad+9 13 )            \ >Tally: 499905<
; 

\ After having parsed width from pad, parse out left-over Tally...
\ ...which might be any value.
: cn.parse.tally ( c-addr u -- u )   \ >Tally: 499905<
  next.space 1+      ( pad+9 13 7 )  \ >Tally: 499905< 7
  SWAP OVER -        ( pad+9 7 6 )   \ >Tally: 499905< 7 6
  -ROT + SWAP        ( pad+16 6 )    \ >499905< 6
  string.to.integer  ( u )           \ 499905
;

: cn.parse.hdr ( fileid -- u ) \ Store row_chars, get tally of primes
  PAD 81 ROT READ-LINE S" cn.parse.hdr READ-LINE" throw.file  ( u flg )
  IF                    ( 22 )       \ Assuming PAD = >Width: 8 Tally: 499905<
    cn.parse.width      ( 6 )        \ row_chars = 8
    cn.parse.tally      ( 499905 )   \ tally
  ELSE CR ." Oops! End of file at cn.parse.hdr " CR
  THEN
  DUP 0= IF CR ." Oops! Tally missing from header. " CR THEN
;

\ Calculate file position from bottom-of-file
: cn.up.fm.btm
  >R
  DUP FILE-SIZE S" cn.up.fm.btm FILE-SIZE" throw.file          ( fileid u ud )
  \ CR ." File-size = " 2DUP D. CR
  row_chars 1+           ( fileid file-size bytes/row )   \ inclusive of newline
  R>                     ( fileid file-size bytes/row prime-sought )
  M*                     ( fileid file-size prime-offset )           \ offset from bottom-of-file
  D-                     ( fileid prime-location )
;

\ Calculate file position from top-of-file
: cn.dn.fm.hdr
  >R
  DUP FILE-POSITION S" cn.dn.fm.top FILE-SIZE" throw.file          ( fileid u ud )
  \ CR ." File-size = " 2DUP D. CR
  row_chars 1+           ( fileid file-size bytes/row )   \ inclusive of newline
  R>                     ( fileid file-size bytes/row prime-sought )
  M*                     ( fileid file-size prime-offset )           \ offset from bottom-of-file
  D+                     ( fileid prime-location )
;

\ Read line into hex_p, returning count of chars read
: cn.get.line ( fileid u -- u ) 
  DUP 0> 
  IF   cn.up.fm.btm
  ELSE cn.dn.fm.hdr
  THEN
  2 PICK REPOSITION-FILE 
  S" cn.get.prime REPOSITION-FILE" throw.file 
  hex_p 255 ROT READ-LINE
  S" cn.get.line READ-LINE" throw.file ( u flg )
  IF           
  ELSE CR ." Oops! End of file at cn.get.prime " CR
  THEN
;
 
: cn.get.prime      ( u fileid )
  SWAP OVER         ( fileid u fileid )
  cn.parse.hdr      ( fileid u u )
  \ CR ." Tally = " DUP . CR
  MOD               ( fileid u )
  \ CR ." Nth = " DUP . CR
  cn.get.line       ( u ) \ Line as HEX in hex_p
;

: cn.get.c ( u -- u ) \ random -- congruent prime
  \ CR CR ." Source file (congruent): " path_c COUNT TYPE CR
  open.file.c
  fd_c cn.get.prime
  close.file.c
;

: cn.get.n ( u -- u ) \ random -- non-congruent prime
  \ CR CR ." Source file (non-congruent): " path_n COUNT TYPE CR
  open.file.n
  fd_n cn.get.prime
  close.file.n
;

: cn.get.p ( u -- u ) \ random -- a prime
  random 2 MOD 0=
  IF   cn.get.c
  ELSE cn.get.n
  THEN
; 

\ Fetch the Nth-from-bottom prime from out of a sifted file.
\ u1 = 10|11|12 ... 48|49|50 the ** in C_10.hexp or N_11.hexp files, etc.
\ u2 = 543210 the Nth prime from bottom of said file
\ 0|1|2 for whether a C_*.hexp, N_*.hexp. Zero for when don't care which
: cn.prime ( u1 u2 0|1|2 -- u ) 
  ROT TO out_#
  file.paths.load
  CASE
    0 OF cn.get.p ENDOF \ Don't care whether congruent or not
    1 OF cn.get.c ENDOF \ Get congruent
    2 OF cn.get.n ENDOF \ Get non-congruent
  ENDCASE
  hex_p SWAP            \ String & count for use by word a.load.hex in file math.fs
;



\ Pillage primes in groups of 600 from website below...
\ http://compoasso.free.fr/primelistweb/page/prime/liste_online_en.php  

\ Work around one aspect of file.paths.load from above ...
\ ... preserving it as-is for use by earlier words above.
: cff.open.file.p ( u -- )
  DUP 0=
  IF
    DROP
    path_dir COUNT S" /export.txt"  cs.append path_p path.load
  ELSE 
    >R
    path_dir COUNT S" /export(" cs.append R> 0 <# #S #>  cs.append S" ).txt"  cs.append path_p path.load
  THEN
  open.file.p
;

\ Files from compoasso.free.fr have no header or blank lines
\ Read a full line of 10 primes onto PAD
: cff.get.line ( -- c )
  PAD 123 fd_p READ-LINE                 ( u flg ior )
  S" cff.get.line READ-LINE" throw.file  ( u flg )
  DROP    
;

0 VALUE cff_prime_last

\ Parse out columns of numbers from line
: cff.parse.numbers ( c i -- ) \ Count of chars read into PAD
  >R PAD SWAP
  9 0 DO split.on.tab >us >us LOOP
  string.to.integer
  9 0 DO us> us> string.to.integer LOOP
  R> 0=                \ Index from loop of prior word
  IF   DROP 9          \ First prime in file is dup of last prime in prior file
  ELSE 10
  THEN
  0 DO
    sift.congruent  ( u -- u fd )
    >R
    DUP TO cff_prime_last                     \ So can tell user final prime processed
    HEX 0 <# #S #> DECIMAL     ( c-addr cnt ) \ 2DUP TYPE SPACE
    R> WRITE-LINE
    S" cff.parse.lines WRITE-LINE" throw.file
  LOOP
;

\ Read in lines, parse for numbers, sift BBS congruent
: cff.parse.lines ( -- )
  60 0 DO
    cff.get.line           ( c )         \ Chars read
    DUP 0>                 ( c 0|1 )     \ Not an empty line
    PAD c@ ascii.decimal?  ( c 0|1 0|1 ) \ Has numbers? Ref defs.fs
    AND                    ( c 0|1 )     \ Both at once?
    IF I cff.parse.numbers (  )          \ Get from PAD
    ELSE DROP
    THEN
  LOOP
;

\ Close output files
: cff.outputs.close
  fd_c FLUSH-FILE S" cff.sift.primes FLUSH-FILE" throw.file
  fd_n FLUSH-FILE S" cff.sift.primes FLUSH-FILE" throw.file
  close.file.n
  close.file.c
;

\ Create output files
: cff.outputs.create
  create.file.c
  create.file.n
  S" Width: Tally: " 2DUP
  fd_c WRITE-LINE S" cff.out.files? WRITE-LINE for fd_c" throw.file
  fd_n WRITE-LINE S" cff.out.files? WRITE-LINE for fd_n" throw.file
;

: cff.outputs.fresh?
  999999                \ A million less 1. Will actually be 1,000,330
  n_tally c_tally +     \ Total primes sifted
  < IF                  \ 1 million primes sifted?
    cff.outputs.close   \ Close current ouput files
    0 TO c_tally        \ Zero both tallies
    0 TO n_tally
    out_# 1+ TO out_#   \ Increment total millions sifted
    file.paths.load 
    cff.outputs.create  \ Create new output files
  THEN
;

\ Similar to t5k.sift.files except source files are...
\ ...export(**).txt as downloaded from the compasso.free.fr webpage
\ Append to files C_**.hexp and N_**.hexp where ** is numeric of
\ top int on stack.
: cff.sift.files ( 9999999 0 51 -- ) \ 168 for 2 when ready
  TO out_#
  0 TO n_tally
  0 TO c_tally
  file.paths.load 
  cff.outputs.create  \ Create new output files
  DO
    cff.outputs.fresh?              \ New outputs at start and every 1 million sifted
    I cff.open.file.p               \ Fresh input file, named like 'export(0).txt'
    CR ." Sifting 599 primes from " \ Assure user script has not stalled.
    path_p COUNT TYPE CR
    cff.parse.lines                 \ Sift primes in file
    close.file.p                    \ Close current input file
    0 delete.file.p                 \ Clean up on the go? 0|1 is flag
  LOOP
  cff.outputs.close
  CR ." Final prime = " cff_prime_last . CR
;

120000 MS \ Delay 2 minutes

1670 2 *
0
51 cff.sift.files







FALSE [IF] \ True for testing

  CR CR ." TESTING WORDS FROM primes_bbs.fs " CR

  FALSE [IF]
    CR ." This will take BLOODY AGES to finish!"
    CR ." For each of the 10th through 50th MILLION primes, two 4MB-plus files will be generated..."
    CR ." C_**.hexp containing primes congruent to MOD 3 and MOD 4, as needed by Blum Blum Shub"
    CR ." N_**.hexp segregating all the other primes which are not"
    10 50 t5k.sift.files 
    CR
  [THEN]

  CR ." Extracting single primes from out of *.hexp files."

  46 -1 1 cn.prime CR TYPE \ 2nd prime after header in 46th million primes that are BBS congruent
  47  0 2 cn.prime CR TYPE \ 1st prime after header in 47th million primes that aren't congruent
  48  1 0 cn.prime CR TYPE \ Last prime of file in 48th million primes, congruent or not

  : test.cn.prime
    \ Over-shooting file capacity is no issue, as MOD will limit request
    7 0 DO
      50 40 random.range random        0 cn.prime CR TYPE
      50 40 random.range random INVERT 0 cn.prime CR TYPE
    LOOP
  ;

  test.cn.prime

  CR CR ." ALL DONE! " CR

[THEN]
