Just in time processing on a character stream
Given a stream of characters, presumably (simulated) from a keyboard, that contain the separators "formfeed", "linefeed", "tab" and "space" characters. Print out the ith character of the ith tab-field of the ith line of the ith page to reveal a secret password.
Stop processing immediately upon encountering a "!" found uniquely in this i,i,i,i position (least the system self destruct). The "!" may be found/permitted else where however, in which case it should be ignored.
Ideally this can be generalise as follows:
- The separators (formfeed, linefeed, tab, space) provided from a user supplied array and can include additional/alternative separators, e.g. (formfeed, linefeed, ".", "," ," ",...).
- These selection criterial is generalised ith,ith,ith,ith to a boolean function of f(page,line,field,word,...) or f(ith,jth,kth,lth,mth,etc...)
Provide a reasonably interesting message to be decoded, e.g. Silence-Dogood. Your choice.
This task was inspired by the movie "National Treasure" with refers to a "book cipher".
REXX
<lang rexx>/*REXX pgm extracts characters by using a book cipher from a text file. */ parse arg iFID /*optional name of file (book). */ if iFID== then iFID="JIT.TXT" /*Not specified? Then use default*/ errTxt= ' is missing or not a positive integer: ' /*error text string.*/ $='abcdefghijklmnopqrstuvwxyz'; _=$; upper _; $= '0123456789'$ || _ ask='──────────enter four parameters (all positive integers) or QUIT' pag=1; lin=1; @.= /*assume start of page 1, line 1.*/
/*read the entire book from file.*/ do while lines(iFID)\==0 /*process lines from input stream*/ _=translate(linein(iFID),,'9'x) /*obtain a single line from input*/ if pos('c'x,_)\==0 then do /*was a ff (form-feed) detected? */ pag=pag+1 /*bump page counter for next page*/ lin=1 /*reset the line counter to one. */ end /* [↑] handle finding of formfeed*/ @.pag.lin=_ /*obtain a single line from input*/ lin=lin+1 /*bump the line counter. */ end /*while lines···*/ /* [↑] read entire input stream.*/
?= /*define the phrase to be null.*/
do prompt=0; bad=1 /*get J.I.T. positional numbers. */ say ask; pull y /*obtain the numbers from console*/ if y='QUIT' then exit 0 /*the user wants out of this loop*/ y=space(translate(y,$,$||xrange())) /*allow any sep user wants.*/ parse var y a.1 a.2 a.3 a.4 /*parse the pertinent parameters.*/ if a.1=='QUIT' then exit 0 /*the user wants out of this loop*/ if words(y)>4 then do /*did the user have fingeritis ? */ say 'too many parameters entered.' iterate prompt end /*go and try again to get parms. */ do k=1 for 4 /*validate parms {positive ints}.*/ if datatype(a.k,'W') & a.k>0 then iterate /*found a bad parm.*/ say 'parameter ' k errTxt a.k /*issue an error message to term.*/ iterate prompt /*go & ask for another set of #s.*/ a.k=a.k/1 /*normalize the user's number. */ end /*k*/ /* [↑] done with the validations*/ parse var y p l w c /*parse parameters for names. */ x=substr(word(@.p.l,w),c,1) /*extract the character from book*/ if x=='!' then leave /*if the stop char found, done.*/ say right(x '◄─── a letter',46) /*might as well echo char to term*/ ?=? || x /*append the character to phrase.*/ end /*j*/ /* [↑] go & keep building phrase*/
say '═════►' ? /*display letters found from book*/
/*stick a fork in it, we're done.*/</lang>
input supplied to the console (terminal) by the user (the commas are optional):
1, 133, 4, 5 1, 34, 9, 3 1, 1377, 2, 2 1, 4, 8, 4 1, 265, 3, 5 1, 413, 10, 2 1, 10, 12, 1 1, 144, 10, 10 1, 571, 4, 12
output (abbridged)
──────────enter four parameters (all positive integers) or QUIT 1, 133, 4, 5 s ◄─── a letter ──────────enter four parameters (all positive integers) or QUIT · · · ═════► so─true.
The input file used (the IBM jargon file) in the above REXX program can be found here ───► Just_in_time_processing_on_a_character_stream/REXX/JIT.TXT.
Tcl
<lang tcl>package require Tcl 8.6
oo::class create JustInTimeStreamExtract {
variable map counter counters last constructor {{pageSeparator "\f"} {lineSeparator "\n"} {fieldSeparator "\t"}} {
dict set map $pageSeparator NextPage dict set map $lineSeparator NextLine dict set map $fieldSeparator NextField set counter 1 array set counters {page 0 line 0 field 0 char 0} set last ""
}
method emit {char} {
puts -nonewline $char set last $char
} method finished {} {
if {$last ne "\n"} { puts "" }
}
method stream {{channel stdin} {length 1}} {
try { while 1 { set str [read $channel $length] if {[eof $channel]} break foreach c [split $str ""] { if {[dict exists $map $c]} { my [dict get $map $c] } else { my NextChar $c } } } } trap STOP {} { # Do nothing } my finished
}
method NextPage {} {
incr counters(page) array set counters {line 0 field 0 char 0}
} method NextLine {} {
incr counters(line) array set counters {field 0 char 0}
} method NextField {} {
incr counters(field) set counters(char) 0
} method NextChar {char} {
incr counters(char) if {[my PrintThisOne?]} { if {$char eq "!"} { throw STOP "stop character found" } incr counter my emit $char array set counters {page 0 line 0 field 0 char 0} }
}
method PrintThisOne? {} {
tcl::mathop::== $counter $counters(page) $counters(line) $counters(field) $counters(char)
}
}</lang> Demonstration of use: <lang tcl>[JustInTimeStreamExtract new] stream [open "sample.txt"]</lang>