#! /home/dat/mira/states/src/miranda/mira -exec
|| Contributed by John Cupitt, University of Kent

||A while ago Steve Hill (I think) appealed for useful Miranda programs
||-- well, here is the greatest productivity aid for Miranda hackers
||since ball-point pens.  A 'vi' type filter to *rebox your comments*!!
||Amazing. It turns dull, unexciting notes like:

|| Given a node in the tree,
||examine the branches and chose the exit with the
|| highest score.

||into:

||----------------------------------------------------------------------||
|| Given a node in the tree, examine the branches and chose the exit 	||
|| with the highest score.						||
||----------------------------------------------------------------------||

||Any comments welcome -- my Miranda is not as hot as it could be ...

||John

||----------------------------------------------------------------------||
|| Box up Miranda comments. A filter from stdin to stdout
||	do_box :: [char] -> [char]					||
||		- Strip ||s, reformat, rebox.				||
||----------------------------------------------------------------------||

main = [Stdout (do_box $-)]

||----------------------------------------------------------------------||
|| Reboxing done in a pipeline of five stages.				||
||	- Split the input into lines					||
||	- Strip '||'s from input					||
||	- Lex the input, breaking into tokens				||
||	- Rejig tokens to produce fmted type output			||
||	- Output tokens as [char] with a box drawn around them		||
|| Formatting rules:							||
||	- Lines starting '||-' are deleted				||
||	- Leading & trailing '||' removed				||
||	- Lines starting with a tab are not reformatted			||
||	- Blank lines are 'new paragraph'				||
||----------------------------------------------------------------------||

||----------------------------------------------------------------------||
|| First a few types and useful little functions.			||
||----------------------------------------------------------------------||

|| Useful constants
outWid = 68			|| Width of the text in our boxes
boxWid = 72			|| Size of the box we draw

|| A token
tok ::=	Word [char] | Newpara | Line [char]

|| Useful character classifier
whitespace :: char -> bool
whitespace ch
      =	True, if ch = '\n' \/ ch = '\t' \/ ch = ' '
      =	False, otherwise

|| An edge of a box boxWid across
edge :: [char]
edge  =	"||" ++ (rep (boxWid-2) '-') ++ "||\n"

|| Find the length of a line containing tabs
len :: [char] -> num
len str
      = len' 0 str
	where
	len' n []
	      = n
	len' n (a:rest)
	      =	len' (n+tab_space) rest, if a = '\t'
	      =	len' (n+1) rest, otherwise
		where
		tab_space
		      =	8 - (n mod 8)

|| Useful when doing output --- only attach first param if its not [].
no_blank :: [char] -> [[char]] -> [[char]]
no_blank a b
      = a : b, if a ~= []
      = b, otherwise

||----------------------------------------------------------------------||
|| The main function. Call from a shell script in your /bin directory	||
|| looking something like:						||
||	#! /usr/bin/mira -exec						||
||	main = [Stdout (do_box $-)]					||
||	%include "../mira/box/box.m"					||
||----------------------------------------------------------------------||

do_box :: [char] -> [char]
do_box input
      =	edge ++ rejig input ++ edge
	where
	rejig =	re_box . format . lex_start . strip_start . split

||----------------------------------------------------------------------||
|| The first stage in processing. Split the input [char] into lines as	||
|| [[char]].								||
||----------------------------------------------------------------------||

|| Split the text into a list of lines
split :: [char] -> [[char]]
split input
      =	split' [] input
	where
	split' sofar (a:input)
	      =	sofar : split input, if a = '\n'
	      = split' (sofar ++ [a]) input, otherwise
	split' sofar []
	      = no_blank sofar []			|| No extra blank lines!

||----------------------------------------------------------------------||
|| The next stage ... strip old '||'s from the input. Remove:		||
||	- Lines starting '||-'						||
||	- Strip leading '||'s						||
||	- Strip trailing '||'s & trailing spaces			||
||----------------------------------------------------------------------||

|| At the start of a line:
strip_start :: [[char]] -> [[char]]
strip_start ([]:input)
      =	[] : strip_start input			|| Keep blank lines
strip_start (('|':'|':line):input)
      = strip_start' line
	where
	strip_start' ('-':rest)
	      = strip_start input		|| Strip '||---||' lines
	strip_start' rest
	      =	strip_rest rest input		|| Strip leading '||'
strip_start (line:input)
      = strip_rest line input			|| Pass rest through
strip_start []
      = []

|| Scan along the rest of the line looking for trailing '||'s to strip.
strip_rest :: [char] -> [[char]] -> [[char]]
strip_rest line input
      =	strip_rest' (rev line) input
	where
	strip_rest' ('|':'|':rest) input
	      =	strip_rest' rest input			|| Strip trailing ||
	strip_rest' (x:rest) input
	      = strip_rest' rest input, if whitespace x
	strip_rest' line input
	      =	(rev line) : strip_start input

|| Efficient(ish) reverse
rev list
      =	rev' [] list
	where
	rev' sofar (a:x)
	      =	rev' (a:sofar) x
	rev' sofar []
	      = sofar

||----------------------------------------------------------------------||
|| The next stage ... Break the input into Word, Newpara and Line	||
|| tokens. Newpara for blank lines and line starting with space; Line	||
|| for lines starting with a tab.					||
||----------------------------------------------------------------------||

|| At the start of a line.
lex_start :: [[char]] -> [tok]
lex_start ([]:input)
      = Newpara : lex_start input		|| Preserve blank lines
lex_start (('\t':rest):input)
      =	Line ('\t':rest) : lex_start input	|| Don't format tab lines
lex_start (line:input)
      = lex_rest (strip_ws line) input		|| Lex to eol
lex_start []
      =	[]

|| In the middle of a line. Try to take words off the front of what we
|| have so far. 
lex_rest :: [char] -> [[char]] -> [tok]
lex_rest [] input
      =	lex_start input
lex_rest sofar input
      =	Word wd : lex_rest (strip_ws rest) input
	where
	(wd, rest)
	      =	break_word sofar

|| Strip ws from the start of the line
strip_ws (a:input)
      =	(a:input), if ~whitespace a
      = strip_ws input, otherwise
strip_ws []
      =	[]

|| Break the word from the front of a line of text. Return the remains
|| of the line along with the word.
break_word :: [char] -> ([char], [char])
break_word (a:line)
      =	([a] ++ rest, tag), if ~whitespace a
      =	([], (a:line)), otherwise
	where
	(rest, tag)
	      =	break_word line
break_word []
      =	([],[])

||----------------------------------------------------------------------||
|| Almost the last stage ... Turn [tok] back into [[char]]. Format	||
|| onto outWid character lines.						||
||----------------------------------------------------------------------||

format :: [tok] -> [[char]]
format input
      =	format' [] input
	where
	format' sofar (Word wd:rest)
	      =	format' (sofar ++ " " ++ wd) rest, if #sofar + #wd < outWid
	      = sofar : format' (" " ++ wd) rest, otherwise
	format' sofar (Newpara:rest)
	      = no_blank sofar ([] : format rest)
	format' sofar (Line line:rest)
	      = no_blank sofar (line : format rest)
	format' sofar []
	      = no_blank sofar []

||----------------------------------------------------------------------||
|| The final stage. Box up a list of formatted lines. Try to be clever	||
|| about using tabs on the ends of lines.				||
||----------------------------------------------------------------------||

|| Draw a box boxWid across.
re_box :: [[char]] -> [char]
re_box (line:rest)
      =	"||" ++ line ++ padding ++ "||\n" ++ (re_box rest)
	where
	padding
	      =	rep n_tab '\t'
	n_tab
	      =	(boxWid - line_length + 7) div 8
	line_length
	      =	len ("||" ++ line)
re_box []
      =[]