box.m
|| 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. Export: ||
|| do_box :: [char] -> [char] ||
|| - Strip ||s, reformat, rebox. ||
||----------------------------------------------------------------------||
%export 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
|| Make a string of chars.
nofthem :: num -> char -> [char]
nofthem 0 ch
= []
nofthem n ch
= ch : nofthem (n-1) ch
|| An edge of a box boxWid across
edge :: [char]
edge = "||" ++ (nofthem (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/l/mira -exp ||
|| [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
= nofthem n_tab '\t'
n_tab
= (boxWid - line_length + 7) div 8
line_length
= len ("||" ++ line)
re_box [] = []