// GPARSE.SR	Paragraph parsing and unparsing

get "BRAVO.DF"
get "CHAR.DF"
get "GINN.DF"
get "DISPLAY.DF"
get "HEAP.DF"

// Incoming procedures

external
	[
	bsearch
	getint
	binsearcha
	getvch
	putvch
	mapscrcp
	errhlt
	stcopy
	stnum
	stappend
	stsize
	insertstring
	deletea
	hpalloca
	hpalloc
	move
	movec
	enww
	ugt
	ult
	readsel
	stequal
	cpadjust2
	invalidatedisplay
	receivechange
	backscan
	specstate
	nextspecstate
	gcspecs
	freespec
	ckspecs
	cpadjustlist
	wipedoc
	makepara
	deletepara
	createdocp
	bubblesegs
	growlist
	discardspecs
	acquirespecs
	replacespec
	forgetspec
	nulltrailer
	uadjust
	specdetails
	setpagecp
	pagecp
	cppage
	macpage
	makepage
	makespec
	cpc
	]

// Incoming statics

external
	[
	xleftmargstd
	xrightmargstd
	rgpara
	rgspec
	rgdlfirst
	rgdllast
	rgmaxdl
	macww
	rgdoc
	vdoc
	vcp
	vchremain
	rgmaccp
	rgcpfdispl
	rgcplast
	vxleftmarg
	vxrightmarg
	cpscrt
	vdlhint
	vinsertk
	mdoc
	mphd
	rgfcode
	rgview
	rgprogram
	vmakelock
	vpzone
	]

// Outgoing procedures

external
	[
	parsespec
	unparsespec
	cppara
	cpparabounds
	parabounds
	paradetails
	setparacp
	paracp
	setparaspec
	gotparaspec
	paraspec
	macpara
	lastparacp
	wholeparas
	]

// Outgoing statics

// Local statics

// Paragraph format is <body><trailer> where <body> has no CRs
// 	and trailer format is ctrlZ then "jn7b12B14u7bU" then CR
// means 5 changes starting with Justified text,
//	7 chars later turn on Bold
//	12 chars later turn off bold
//	14 chars later turn on Ul
//	7 chars later turn on Bold and turn off ul
// n means no change since last version; N means has changed
// there is an optional change count in front of the trailer;
//	if not present it is estimated

let parsespec(doc, para, mail, okrobdisplay; numargs N) = valof
[
let num = nil
let tex, b, e = nil, nil, nil
tex = paracp(doc, para)
e = paracp(doc, para+1) - 1
unless backscan(doc, e, true, chtrailer) do errhlt("MTR")
b = vcp
if ult(b, tex) then errhlt("MTX")
vcp = b + 1
vchremain = 0 ;
vdoc = doc ;
let char = getvch() ;
let siz = $0 le char & char le $9? 3 + getint(doc, 10, char, lv char), (e-b+1) rshift 1
let lim = siz + 2
if vmakelock then
	if ugt((2*lim+specbase)*(mail? 3,2)+displaybuf,
		vpzone >> ZONE.cfree) then
			resultis -1
let spec = makespec(lim, N eq 4 & okrobdisplay)
spec >> SPEC.trailerlength = e + 1 - b
setparaspec(doc, para, spec)
spec >> SPEC.dent = 0
spec >> SPEC.lmarg = 0
spec >> SPEC.rmarg = 0
let info = spec + firstformat
let looks = spec + specbase
let changes = looks + lim
let tfs = 0
let tlook = 0
	[
	let fcode = 0 le char & char le 127? rgfcode ! char, -1
	let w = fcode << FCODE.wordn
	let bn = fcode << FCODE.bitn
	let m = #100000 rshift bn
	let un = fcode << FCODE.un
	switchon fcode << FCODE.kind into
		[
		case knormal: 
			spec >> SPEC.marker = un ;
			char = getvch() ; loop
		case kdigit:
			num = getint(doc, 10, char, lv char) ;
			looks ! tfs = tlook
			tfs = tfs + 1
			if tfs ge siz then errhlt("TMS")
			looks ! tfs = tlook
			changes ! tfs = num + changes ! (tfs-1)
			loop
		case kcr: break
		case klook: tlook = un? tlook & not m, tlook % m
			endcase
		case kquad: info ! w = bn ; endcase
		case kmeasure:
			num = getint(doc,10,$0,lv char)
			info ! w = num
			loop // $$ bug fix
		default: errhlt("UFC")
		]
	char = getvch() ;	
	] repeat
looks ! tfs = tlook
siz = tex eq b? 2, tfs+3
if siz gr lim then errhlt("TmS") ;
spec >> LIST.siz = siz
changes ! (siz-2) = b-tex
changes ! (siz-1) = e+1-tex
if mail then receivechange(mail, doc, para)
resultis gotparaspec(doc, para)
]

and unparsespec(doc, para, spec) = valof
[
if vinsertk % not spec >> SPEC.dirty % rgprogram ! doc then
	resultis spec
spec >> SPEC.dirty = 0
let str = vec (maxtrailerl+1)/2 ;
let stn = vec (maxtrailerl+1)/2 +4 ;
stcopy(str, "") ;
let siz, looks, changes = nil, nil, nil
specdetails(spec, lv siz)
let rcpold = 0 ;
let tfold = 0 ;
let tfnew, tfdif, rcpnew = nil,nil,nil
unparsespec2(spec >> SPEC.dent, "d", str, stn)
unparsespec2(spec >> SPEC.lmarg, "l", str, stn)
unparsespec2(spec >> SPEC.rmarg, "r", str, stn)
unparsespec3(spec >> SPEC.quad, 0, table[ 0; $j; $c ] , str)
stappend(str, "N");
let last = siz - 3;
for tfs = 0 to last do
	[
	rcpnew = changes ! tfs ;
	tfnew = looks ! tfs ;
	tfdif = tfnew xor tfold ;
	if tfdif then
		[
		if tfs then
			[
			stnum(stn, rcpnew - rcpold)
			stappend(str, stn) ;
			]
		for char = $A to $z do
		    if (rgfcode ! char) << FCODE.kind eq klook then
			[
			let fcode = rgfcode ! char
			let m = #100000 rshift (fcode << FCODE.bitn)
			let un = fcode << FCODE.un? 0, m
			if (tfdif & m) ne 0 & (tfnew & m) eq un then
				[
				let onecharstring = #400 + char
				stappend(str, lv onecharstring)
				]
			]
		tfold = tfnew
		rcpold = rcpnew ;
		]
	]
let tex, b, e = nil, nil, nil
parabounds(doc, para, lv tex, lv b, lv e)
let hstart = b+1
let hfinish = e-1
let newhlen = stsize(str) + 2
if newhlen eq spec >> SPEC.trailerlength then
	[
	if newhlen eq 2 then resultis spec
	readsel(stn, doc, hstart, hfinish, maxtrailerl)
	if stequal(str, stn) then resultis spec
	]
if ((rgview ! doc) & looktrailermask) ne 0 then
	invalidatedisplay(doc, b, vdlhint)
if cpc(hstart, hfinish) le 0 then deletea(doc, hstart, hfinish)
insertstring(doc, hstart, str)
spec = gotparaspec(doc, para)
spec >> SPEC.trailerlength = newhlen
specdetails(spec, lv siz)
changes ! (siz-1) = newhlen + b-tex
resultis spec
]

and unparsespec2(relativevalue, char, str, stn) be
[
if relativevalue eq 0 then return
stappend(str, char)
stnum(stn, relativevalue)
stappend(str, stn)
]

and unparsespec3(itsfmt, stdfmt, chartable, str) be
[
if itsfmt eq stdfmt then return
let x = "X"
x >> rh = chartable!itsfmt
stappend(str, x)
]

and cppara(doc, cp) = bsearch(rgpara ! doc, cp)

and cpparabounds(doc, cp, ptextstart, ptrailerstart, ptrailerend) =
   parabounds(doc, cppara(doc, cp),
	ptextstart, ptrailerstart, ptrailerend)

and parabounds(doc, para, ptextstart, ptrailerstart, ptrailerend) = valof
[
let spec = paraspec(doc, para)
@ptextstart = paracp(doc, para)
@ptrailerend = paracp(doc, para+1) - 1
@ptrailerstart = @ptrailerend - spec >> SPEC.trailerlength + 1
resultis para
]

and paradetails(doc, para, vector, cp1, cp2, cp3, cp4;numargs N) be
[
// To use:
// let spec,siz,looks,changes[,rcp1,r1[,rcp2,r2[,...]]] = nil,nil,...,nil
// paradetails(doc,para,lv spec[,cp1[,cp2[,...]]])
// r ge siz-2 means rcp is in the trailer
let tex = paracp(doc, para)
let spec = paraspec(doc, para)
vector ! 0 = spec
let siz = specdetails(spec, vector+1)
let j = 4
for i = 0 to N-3 do
	[
	let rcp = ((lv cp1)!i) - tex
	vector ! j = rcp
	vector ! (j+1) = rcp ls 0? 0, binsearcha(vector!3, siz-1, rcp)
	j = j + 2
	]
]

and setparacp(doc, para, cp) be
	(rgpara ! doc) ! (para + listbase) = cp

and paracp(doc, para) = valof
[
// Inverse of cppara
// Given a para, find the cp of the start of its text
resultis (rgpara ! doc) ! (para + listbase)
]

and setparaspec(doc, para, spec) be
	[
	if spec & not spec << odd then
		[
		spec >> SPEC.doc = doc
		spec >> SPEC.para = para
		]
	(rgspec ! doc) ! (para + listbase) = spec
	]
 
and gotparaspec(doc, para) = (rgspec ! doc) ! (para + listbase)
 
and paraspec(doc, para) = valof
[
// Given a paragraph number within a document, find its spec --
//	if none exists, invoke a parse to create one.
let w = (rgspec!doc)!(para+listbase) ;
if w & not w << odd then resultis w ;
resultis parsespec(doc, para, w)
]

and macpara(doc) = (rgpara ! doc) >> LIST.siz

and lastparacp(doc) = paracp(doc, macpara(doc)-2)

and wholeparas(doc, cp1, cp2) =
	cp1 eq paracp(doc, cppara(doc, cp1)) &
	cp2+1 eq paracp(doc, cppara(doc, cp2+1))? -1, 0