* snopea -- a tiny "Plain Old Document" formatter for SNOBOL4
	sp.version = '$Id: snopea.sno,v 1.34 2020-12-16 22:19:03 phil Exp $'

* NOTE!  The directives are only *inspired* by "POD" format

-include 'basename.sno'
-include 'host.sno'

*	snopea.debug = 1

* character entity handling... A mishmash of troff and HTML!
	snopea.charpats = TABLE()

	snopea.charmaps = TABLE()
	snopea.charpats['roff'] = "''" | "``" | '\=' |
+			'&' ANY(&LCASE) SPAN(&LCASE) ';'
*	Turn \- into \[en]?
	snopea.charmaps['roff'] = snopea.charmap.roff = TABLE()
	snopea.charmap.roff["``"] = '\*(lq'
	snopea.charmap.roff["''"] = '\*(rq'
	snopea.charmap.roff["&lt;"] = '<'
	snopea.charmap.roff["&gt;"] = '>'
	snopea.charmap.roff["&pi;"] = '\*(pi'
	snopea.charmap.roff["&amp;"] = '&'
	snopea.charmap.roff["\="] = '='

	snopea.charpats['html'] = "''" | "``"  | '\' ANY("e-&|~'`=")
	snopea.charmaps['html'] = snopea.charmap.html = TABLE()
	snopea.charmap.html["``"] = '&ldquo;'
	snopea.charmap.html["''"] = '&rdquo;'
	snopea.charmap.html["\e"] = '\'
	snopea.charmap.html["\-"] = '&ndash;'
	snopea.charmap.html["\&"] = ''
	snopea.charmap.html["\|"] = '&thinsp;'
	snopea.charmap.html["\~"] = '&nbsp;'
	snopea.charmap.html["\'"] = "'"
	snopea.charmap.html["\`"] = '`'
	snopea.charmap.html["\="] = '='

	snopea.tab = CHAR(9)
	snopea.ws = SPAN(' ' snopea.tab)
	snopea.bws = BREAK(' ' snopea.tab)

	HOST() POS(0) BREAK(':') ':' BREAK(':') ':' REM . snopea.snovers

	DEFINE('snopea(ifile,ofile,page,sect,format)'
+			'font,efont,endlist,lsep,pre,chr,ctab,numb,t1,t2,pat,'
+			'date,text,snomod,line,inpea,needspace,after,'
+			'oline,text,iunit,ounit,lbl,cmd') :(snopea.end)
snopea
	iunit = IO_FINDUNIT()
	INPUT(.in, iunit,, ifile)			:S(sp.oo)
	TERMINAL = 'snopea: could not open ' ifile	:(FRETURN)

sp.oo	DELETE(ofile)
	ounit = IO_FINDUNIT()
	OUTPUT(.out, ounit,, ofile)			:S(sp.begin)
	TERMINAL = 'snopea: could not open ' ofile
	ENDFILE(iunit)
	DETACH(.in)					:(FRETURN)

sp.begin
	TERMINAL = IDENT(page) "snopea: need page name"	:S(FRETURN)
	TERMINAL = IDENT(sect) "snopea: need section"	:S(FRETURN)
	format = IDENT(format) 'roff'

	page = REPLACE(page, &LCASE, &UCASE)
	date = HOST(HOST_VERSION_DATE)
	cmd = 'head'					:(sp.dispatch2)

****************
sp.top	line = in					:F(sp.eof)
* strip leading stars
	line POS(0) SPAN('*') =
* check for =cmd
	cmd = 'XXX'
	line POS(0) '=' SPAN(&LCASE) . cmd REM . rest	:S(sp.cmd)
* no command:
	IDENT(inpea)					:S(sp.top)
* in pea mode, no command:
	IDENT(line)					:S(sp.eop)
* non-empty line, output paragraph break if needed
	cmd = DIFFER(needspace) 'para'			:S(sp.cmd)
* handle text, clear output line holder
sp.text	oline =
* font loop: here back from 'font' dispatch
sp.floop
	line POS(0) BREAKX('BICL') . pre 
+			ANY('BICL') . font '<' BREAK('>') . txt '>' =
+							:f(sp.fdone)
* here with pre, font & txt:
	cmd = 'font'					:(sp.dispatch)

* done with fonts, perform character substitutions
sp.fdone
	line = oline line
	oline =

* loop for character substitutions
sp.cloop
	line POS(0) ARB . pre snopea.charpats[format] . chr = :f(sp.cdone)
	oline = oline pre snopea.charmaps[format][chr]	:(sp.cloop)

* done with character substitutions
sp.cdone
	cmd = 'oline'
	oline = oline line				:(sp.dispatch2)

* here after 'oline' command
sp.out	out = vdiffer(lsep)
	text = 1
	:(sp.top)

****
* saw end of input file
sp.eof	cmd = 'eof'					:(sp.dispatch)

****
* saw end of paragraph (blank line)
sp.eop	needspace = text
	text =						:(sp.top)

****************
* here to dispatch to a command, clears argument
sp.dispatch
	rest =
* here with user command, remove leading spaces on argument
sp.cmd	rest POS(0) snopea.ws =
	inpea = 1

**** dispatch a command without altering "inpea"
* NOTE!! commands may return to someplace OTHER than sp.top!!!
sp.dispatch2
	lsep =
	TERMINAL = DIFFER(snopea.debug) '>>> ' cmd
	lbl = 'sp.' format '.' cmd
	LABEL(lbl)					:S($lbl)
	TERMINAL = 'Unknown command ' cmd		:(sp.top)

****
* here from 'eof' command processing
sp.close
	ENDFILE(iunit)
	ENDFILE(ounit)
	DETACH(.in)
	DETACH(.out)					:(return)

****************
* command processing for *roff:

sp.roff.head
	out = '.\" generated by ' sp.version

*	if nroff, give "ragged right" output':
	out = '.if n .ad l'
	out = ".ie '\*[.T]'ascii' \{\"
	out = '.	ds lq \&"\"'
	out = '.	ds rq \&"\"'
	out = ".	ds pi \fIpi\fP"
	out = ".\}"
	out = ".el \{\"
	out = ".	ds rq ''"
	out = ".	ds lq ``"
	out = ".	ds pi \[*p]"
	out = ".\}"

*	disable hyphenation:
	out = '.nh'

	out = '.TH ' page ' ' sect ' "' date '" "' snopea.snovers '"'
+		' "CSNOBOL4 Manual"'

	indent = 4
	text =
	regfont = '\fR'					:(sp.top)

sp.roff.pea						:(sp.top)

* internal (blank line seen)
sp.roff.para
	out = ".PP"
	needspace = numb =				:(sp.text)

sp.roff.cut
	inpea =						:(sp.top)

sp.roff.font
	oline = IDENT(font, 'L') oline pre txt		:s(sp.floop)
	font = (IDENT(font, 'C') '\f(CW', '\f' font)
	oline = oline pre font txt '\fP'		:(sp.floop)

sp.roff.oline
	oline = DIFFER(oline ? POS(0) ANY(".'")) '\&' oline
	out = oline					:(sp.out)

sp.roff.sect
	rest POS(0) snopea.ws =
	out = '.SH "' rest '"'
	out = '.nh'
	needspace =					:(sp.top)

sp.roff.subsect
	rest POS(0) snopea.ws =
	out = '.SS "' rest '"'
	out = '.nh'
	needspace =					:(sp.top)

* set indent level for items
sp.roff.indent
	indent = rest
	needspace =					:(sp.top)

* unadorned item
sp.roff.item
	needspace =
	rest POS(0) snopea.ws =
	out = IDENT(rest) '.IP'				:s(sp.top)
	out = '.TP ' indent
	line = rest					:(sp.text)

* bullet item
sp.roff.bull
	out = '.IP \(bu'
	needspace =
	line = rest					:(sp.text)

* numbered item
sp.roff.nitem
	numb = (VDIFFER(numb), 1)
	out = '.IP ' numb
	numb = numb + 1
	needspace =
	line = rest					:(sp.text)

sp.roff.code
* constant width font:
	out = '.ft CW'
* fresh line
	out = '.br'
* soft page break: need 10 lines
	out = '.ne 10'
* indent:
	out = '.RS ' indent
* no hyphenation, formatting:
	out = '.nh'
	out = '.nf'					:(sp.top)

* back to "Roman" typeface:
sp.roff.ecode
	out = '.ft R'
* fill, no hyphenation:
	out = '.fi'
	out = '.nh'
* unindent:
	out = '.RE'					:(sp.top)

sp.roff.break
	out = '.br'					:(sp.top)

sp.roff.table
	out = '.PP'
	rest POS(0) snopea.ws =
	out = DIFFER(rest) '.ta ' rest
	out = '.nf'					:(sp.top)

sp.roff.row
	rest POS(0) snopea.ws =
	line = rest					:(sp.text)

sp.roff.etable
	out = '.fi'					:(sp.top)

sp.roff.anchor						:(sp.top)

sp.roff.link
	rest = rest ' '
	rest POS(0) snopea.ws =
	rest POS(0) snopea.bws . t1 =
	rest POS(0) snopea.ws =
	line = (VDIFFER(rest), t1)			:s(sp.text)

* ADD NEW OPS ABOVE THIS LINE

sp.roff.eof						:(sp.close)

****************
* command processing for html

sp.html.head
	out = '<!-- generated by ' sp.version ' -->'

	out = '<html>'
	out = '<head>'
	htext =  page '(' sect ') | ' snopea.snovers ' | ' date
	out = ' <title>' htext '</title>'
* _COULD_ output <style> here, but trying to work with lynx!
	out = '</head>'
	out = '<body>'
	out = '<h1>' htext '</h1>'
	indent = 4
	text =

sp.html.pea						:(sp.top)

* internal (blank line seen)
sp.html.para
	out = VDIFFER(endlist)
	out = '<p>'
	needspace = endlist =				:(sp.text)

sp.html.cut						:(sp.roff.cut)

sp.html.font
	oline = IDENT(font, 'L') oline pre '<a href="' txt '">' txt '</a>' :s(sp.floop)
	font = ident(font, 'R')				:s(sp.html.font2)
	font = ident(font, 'C') 'TT'
sp.html.font2
	efont = ident(font)				:s(sp.html.font3)
	efont = '</' font '>'
	font = '<' font '>'

sp.html.font3
	oline = oline pre font txt efont		:(sp.floop)

* MASSIVE CROCK!!! detect snobol4 man page refs, and turn into links!!
sp.html.oline
+	t1 =
	pat = POS(0) BREAKX('<') . pre 
+	       ('<B>'
+	         (('snobol4' | 'sdb' | 'snolib' | 'snopea') BREAK('<')) . pg
+               '</B>'
+	        '(' ANY('137') . sec ')'
+              ) . full
sp.html.oline1
+	IDENT(oline)					:s(sp.html.oline2)
	oline pat =					:f(sp.html.oline2)
	t1 = t1 pre '<a href="' pg '.' sec '.html">' full '</a>'
	:(sp.html.oline1)
sp.html.oline2
+	out = t1 oline
	out = VDIFFER(after)
	after =						:(sp.out)

sp.html.sect
	out = vdiffer(endlist)
	endlist =
	out = '<h2>' rest '</h2>'
	needspace =					:(sp.top)

sp.html.subsect
	out = vdiffer(endlist)
	endlist =
	out = '<h3>' rest '</h3>'
	needspace =					:(sp.top)

* set indent level for items
sp.html.indent
	indent = rest
	needspace =					:(sp.top)

* definition list
sp.html.item
	differ(endlist)					:s(sp.html.item2)
	out = '<dl>'
	endlist = '</dl>'
sp.html.item2
* don't REALLY want whitespace immediately after a (sub)section header:
	out = '<p>'
	out = '<dt>'
	after = '<dd>'
	needspace =
	line = rest					:(sp.text)

* bullet item
sp.html.bull
	differ(endlist)					:s(sp.html.bull2)
	out = '<p>'
	out = '<ul>'
	endlist = '</ul>'
sp.html.bull2
	out = '<li>'
	needspace =
	line = rest					:(sp.text)

* numbered item
sp.html.nitem
	differ(endlist)					:s(sp.html.bull2)
	out = '<ol>'
	endlist = '</ol>'				:(sp.html.bull2)

sp.html.code
	out = '<pre>'					:(sp.top)

sp.html.ecode
	out = '</pre>'					:(sp.top)

sp.html.break
	out = '<br>'					:(sp.top)

sp.html.table
	out = '<p>'
	out = '<table>'					:(sp.top)

sp.html.row
	rest = '<tr>' snopea.tab rest
sp.html.rowloop
	rest snopea.tab = '<td>'			:s(sp.html.rowloop)
	line = rest					:(sp.text)

sp.html.etable
	out = '</table>'				:(sp.top)

sp.html.anchor
	rest POS(0) snopea.ws =
	out = '<a name="' rest '"></a>'			:(sp.top)

sp.html.link
	rest = rest ' '
	rest POS(0) snopea.ws =
	rest POS(0) snopea.bws . t1 =
	rest POS(0) snopea.ws =
	line = '<a href="' t1 '">' (VDIFFER(rest), t1) '</a>' :(sp.text)

* ADD NEW OPS ABOVE THIS LINE

sp.html.eof
	out = vdiffer(endlist)
*	output footer?  link to csnobol4 home page??
	out = '</body>'
	out = '</html>'					:(sp.close)

****************

snopea.end
