1if not modules then modules = { } end modules ['strc-num'] = {
2 version = 1.001,
3 comment = "companion to strc-num.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9local format = string.format
10local next, type, tonumber = next, type, tonumber
11local min, max = math.min, math.max
12local insert, remove, copy = table.insert, table.remove, table.copy
13local texsetcount = tex.setcount
14
15
16
17
18local context = context
19
20local allocate = utilities.storage.allocate
21local setmetatableindex = table.setmetatableindex
22local setmetatablecall = table.setmetatablecall
23
24local trace_counters = false trackers.register("structures.counters", function(v) trace_counters = v end)
25local report_counters = logs.reporter("structure","counters")
26
27local implement = interfaces.implement
28
29local structures = structures
30local helpers = structures.helpers
31local sections = structures.sections
32local counters = structures.counters
33local documents = structures.documents
34
35local variables = interfaces.variables
36local v_start = variables.start
37local v_page = variables.page
38local v_reverse = variables.reverse
39local v_first = variables.first
40local v_next = variables.next
41local v_previous = variables.previous
42local v_prev = variables.prev
43local v_last = variables.last
44
45
46
47
48
49
50
51counters.specials = counters.specials or { }
52local counterspecials = counters.specials
53
54local counterranges, tbs = { }, 0
55
56counters.collected = allocate()
57counters.tobesaved = counters.tobesaved or { }
58counters.data = counters.data or { }
59
60storage.register("structures/counters/data", counters.data, "structures.counters.data")
61storage.register("structures/counters/tobesaved", counters.tobesaved, "structures.counters.tobesaved")
62
63local collected = counters.collected
64local tobesaved = counters.tobesaved
65local counterdata = counters.data
66
67local function initializer()
68 collected = counters.collected
69 tobesaved = counters.tobesaved
70 counterdata = counters.data
71end
72
73local function finalizer()
74 for name, cd in next, counterdata do
75 local cs = tobesaved[name]
76 local data = cd.data
77 for i=1,#data do
78 local d = data[i]
79 local r = d.range
80 cs[i][r] = d.number
81 d.range = r + 1
82 end
83 end
84end
85
86job.register('structures.counters.collected', tobesaved, initializer, finalizer)
87
88local constructor = {
89
90 last = function(t,name,i)
91 local cc = collected[name]
92 local stop = (cc and cc[i] and cc[i][t.range]) or 0
93 t.stop = stop
94 if t.offset then
95 return stop - t.step
96 else
97 return stop
98 end
99 end,
100
101 first = function(t,name,i)
102 local start = t.start
103 if start > 0 then
104 return start
105 elseif t.offset then
106 return start + t.step + 1
107 else
108 return start + 1
109 end
110 end,
111
112 prev = function(t,name,i)
113 return max(t.first,t.number-1)
114 end,
115
116 previous = function(t,name,i)
117 return max(t.first,t.number-1)
118 end,
119
120 next = function(t,name,i)
121 return min(t.last,t.number+1)
122 end,
123
124 backward =function(t,name,i)
125 if t.number - 1 < t.first then
126 return t.last
127 else
128 return t.previous
129 end
130 end,
131
132 forward = function(t,name,i)
133 if t.number + 1 > t.last then
134 return t.first
135 else
136 return t.next
137 end
138 end,
139
140 subs = function(t,name,i)
141 local cc = collected[name]
142 t.subs = (cc and cc[i+1] and cc[i+1][t.range]) or 0
143 return t.subs
144 end,
145
146}
147
148local function dummyconstructor(t,name,i)
149 return nil
150end
151
152setmetatableindex(constructor,function(t,k)
153
154
155
156 return dummyconstructor
157end)
158
159local function enhance()
160 for name, cd in next, counterdata do
161 local data = cd.data
162 for i=1,#data do
163 local ci = data[i]
164 setmetatableindex(ci, function(t,s) return constructor[s](t,name,i) end)
165 end
166 end
167 enhance = nil
168end
169
170local function allocate(name,i)
171 local cd = counterdata[name]
172 if not cd then
173 cd = {
174 level = 1,
175
176 numbers = nil,
177 state = v_start,
178 data = { },
179 saved = { },
180 }
181 tobesaved[name] = { }
182 counterdata[name] = cd
183 end
184 cd = cd.data
185 local ci = cd[i]
186 if not ci then
187 for i=1,i do
188 if not cd[i] then
189 ci = {
190 number = 0,
191 start = 0,
192 saved = 0,
193 step = 1,
194 range = 1,
195 offset = false,
196 stop = 0,
197 }
198 setmetatableindex(ci, function(t,s) return constructor[s](t,name,i) end)
199 cd[i] = ci
200 tobesaved[name][i] = { }
201 end
202 end
203 elseif enhance then
204 enhance()
205 end
206 return ci
207end
208
209local pattern = lpeg.P(variables.by)^-1 * lpeg.C(lpeg.P(1)^1)
210local lpegmatch = lpeg.match
211
212function counters.way(way)
213 if not way or way == "" then
214 return ""
215 else
216 return lpegmatch(pattern,way)
217 end
218end
219
220implement {
221 name = "way",
222 actions = { counters.way, context },
223 arguments = "string"
224}
225
226
227function counters.record(name,i)
228 return allocate(name,i or 1)
229end
230
231local function savevalue(name,i)
232 if name then
233 local cd = counterdata[name].data[i]
234 local cs = tobesaved[name][i]
235 local cc = collected[name]
236 if trace_counters then
237 report_counters("action %a, counter %s, value %s","save",name,cd.number)
238 end
239 local cr = cd.range
240 local old = (cc and cc[i] and cc[i][cr]) or 0
241 local number = cd.number
242 if cd.method == v_page then
243
244 number = number - 1
245 end
246 cs[cr] = (number >= 0) and number or 0
247 cd.range = cr + 1
248 return old
249 else
250 return 0
251 end
252end
253
254function counters.define(specification)
255 local name = specification.name
256 if name and name ~= "" then
257
258 local d = allocate(name,1)
259 d.start = tonumber(specification.start) or 0
260 d.state = v_state or ""
261 local counter = specification.counter
262 if counter and counter ~= "" then
263 d.counter = counter
264 d.method = specification.method
265 end
266 end
267end
268
269function counters.raw(name)
270 return counterdata[name]
271end
272
273function counters.compact(name,level,onlynumbers)
274 local cd = counterdata[name]
275 if cd then
276 local data = cd.data
277 local compact = { }
278 for i=1,level or #data do
279 local d = data[i]
280 if d.number ~= 0 then
281 compact[i] = (onlynumbers and d.number) or d
282 end
283 end
284 return compact
285 end
286end
287
288
289
290function counters.previous(name,n)
291 return allocate(name,n).previous
292end
293
294function counters.next(name,n)
295 return allocate(name,n).next
296end
297
298counters.prev = counters.previous
299
300function counters.currentvalue(name,n)
301 return allocate(name,n).number
302end
303
304function counters.first(name,n)
305 return allocate(name,n).first
306end
307
308function counters.last(name,n)
309 return allocate(name,n).last
310end
311
312function counters.subs(name,n)
313 return counterdata[name].data[n].subs or 0
314end
315
316local function setvalue(name,tag,value)
317 local cd = counterdata[name]
318 if cd then
319 cd[tag] = value
320 end
321end
322
323counters.setvalue = setvalue
324
325function counters.setstate(name,value)
326 value = variables[value]
327 if value then
328 setvalue(name,"state",value)
329 end
330end
331
332function counters.setlevel(name,value)
333 setvalue(name,"level",value)
334end
335
336function counters.setoffset(name,value)
337 setvalue(name,"offset",value)
338end
339
340local function synchronize(name,d)
341 local dc = d.counter
342 if dc then
343 if trace_counters then
344 report_counters("action %a, name %a, counter %a, value %a","synchronize",name,dc,d.number)
345 end
346 texsetcount("global",dc,d.number)
347 end
348 local cs = counterspecials[name]
349 if cs then
350 if trace_counters then
351 report_counters("action %a, name %a, counter %a","synccommand",name,dc)
352 end
353 cs(name)
354 end
355end
356
357local function reset(name,n)
358 local cd = counterdata[name]
359 if cd then
360 for i=n or 1,#cd.data do
361 local d = cd.data[i]
362 savevalue(name,i)
363 local number = d.start or 0
364 d.number = number
365 d.own = nil
366 if trace_counters then
367 report_counters("action %a, name %a, sub %a, value %a","reset",name,i,number)
368 end
369 synchronize(name,d)
370 end
371 cd.numbers = nil
372 else
373 end
374end
375
376local function set(name,n,value)
377 local cd = counterdata[name]
378 if cd then
379 local d = allocate(name,n)
380 local number = value or 0
381 d.number = number
382 d.own = nil
383 if trace_counters then
384 report_counters("action %a, name %a, sub %a, value %a","set",name,"no",number)
385 end
386 synchronize(name,d)
387 end
388end
389
390local function check(name,data,start,stop)
391 for i=start or 1,stop or #data do
392 local d = data[i]
393 savevalue(name,i)
394 local number = d.start or 0
395 d.number = number
396 d.own = nil
397 if trace_counters then
398 report_counters("action %a, name %a, sub %a, value %a","check",name,i,number)
399 end
400 synchronize(name,d)
401 end
402end
403
404
405local function setown(name,n,value)
406 local cd = counterdata[name]
407 if cd then
408 local d = allocate(name,n)
409 d.own = value
410 d.number = (d.number or d.start or 0) + (d.step or 0)
411 local level = cd.level
412 if not level or level == -1 then
413
414 elseif level > 0 or level == -3 then
415 check(name,d,n+1)
416 elseif level == 0 then
417
418 end
419 synchronize(name,d)
420 end
421end
422
423local function restart(name,n,newstart,noreset)
424 local cd = counterdata[name]
425 if cd then
426 newstart = tonumber(newstart)
427 if newstart then
428 local d = allocate(name,n)
429 d.start = newstart
430 if not noreset then
431 reset(name,n)
432 end
433 end
434 end
435end
436
437function counters.save(name)
438 local cd = counterdata[name]
439 if cd then
440 insert(cd.saved,copy(cd.data))
441 end
442end
443
444function counters.restore(name)
445 local cd = counterdata[name]
446 if not cd then
447 report_counters("invalid restore, no counter %a",name)
448 return
449 end
450 local saved = cd.saved
451 if not saved then
452
453 elseif #saved > 0 then
454 cd.data = remove(saved)
455 else
456 report_counters("restore without save for counter %a",name)
457 end
458end
459
460local function add(name,n,delta)
461 local cd = counterdata[name]
462 if cd and (cd.state == v_start or cd.state == "") then
463 local data = cd.data
464 local d = allocate(name,n)
465 d.number = (d.number or d.start or 0) + delta*(d.step or 0)
466
467 local level = cd.level
468 if not level or level == -1 then
469
470 if trace_counters then
471 report_counters("action %a, name %a, sub %a, how %a","add",name,"no","no checking")
472 end
473 elseif level == -2 then
474
475 if trace_counters then
476 report_counters("action %a, name %a, sub %a, how %a","add",name,"text","checking")
477 end
478 check(name,data,n+1)
479 elseif level > 0 or level == -3 then
480
481 if trace_counters then
482 report_counters("action %a, name %a, sub %a, how %a","add",name,level,"checking within group")
483 end
484 check(name,data,n+1)
485 elseif level == 0 then
486
487 if trace_counters then
488 report_counters("action %a, name %a, sub %a, how %a","add",name,level,"no checking")
489 end
490 else
491 if trace_counters then
492 report_counters("action %a, name %a, sub %a, how %a","add",name,"unknown","no checking")
493 end
494 end
495 synchronize(name,d)
496 return d.number
497 end
498 return 0
499end
500
501function counters.check(level)
502 for name, cd in next, counterdata do
503 if level > 0 and cd.level == -3 then
504 if trace_counters then
505 report_counters("action %a, name %a, sub %a, detail %a","reset",name,level,"head")
506 end
507 reset(name)
508 elseif cd.level == level then
509 if trace_counters then
510 report_counters("action %a, name %a, sub %a, detail %a","reset",name,level,"normal")
511 end
512 reset(name)
513 end
514 end
515end
516
517local function get(name,n,key)
518 local d = allocate(name,n)
519 d = d and d[key]
520 if not d then
521 return 0
522 elseif type(d) == "function" then
523 return d()
524 else
525 return d
526 end
527end
528
529counters.reset = reset
530counters.set = set
531counters.add = add
532counters.get = get
533counters.setown = setown
534counters.restart = restart
535
536function counters.value(name,n)
537 return get(name,n or 1,'number') or 0
538end
539
540function counters.converted(name,spec)
541 local cd
542 if type(name) == "number" then
543 cd = specials.retrieve("counter",name)
544 if cd then
545 cd = cd.counter
546 end
547 else
548 cd = counterdata[name]
549 end
550 if cd then
551 local spec = spec or { }
552 local numbers = { }
553 local ownnumbers = { }
554 local reverse = spec.order == v_reverse
555 local kind = spec.type or "number"
556 local data = cd.data
557 for k=1,#data do
558 local v = data[k]
559
560 local vn
561 if v.own then
562 numbers[k] = v.number
563 ownnumbers[k] = v.own
564 else
565 if kind == v_first then
566 vn = v.first
567 elseif kind == v_next then
568 vn = v.next
569 elseif kind == v_prev or kind == v_previous then
570 vn = v.prev
571 elseif kind == v_last then
572 vn = v.last
573 else
574 vn = v.number
575 if reverse then
576 local vf = v.first
577 local vl = v.last
578 if vl > 0 then
579
580 vn = vl - vn + vf
581 end
582 end
583 end
584 numbers[k] = vn or v.number
585 ownnumbers[k] = nil
586 end
587 end
588 cd.numbers = numbers
589 cd.ownnumbers = ownnumbers
590 sections.typesetnumber(cd,'number',spec)
591 cd.numbers = nil
592 cd.ownnumbers = nil
593 end
594end
595
596
597
598local function showcounter(name)
599 local cd = counterdata[name]
600 if cd then
601 context("[%s:",name)
602 local data = cd.data
603 for i=1,#data do
604 local d = data[i]
605 context(" (%s: %s,%s,%s s:%s r:%s)",i,d.start or 0,d.number or 0,d.last,d.step or 0,d.range or 0)
606 end
607 context("]")
608 end
609end
610
611
612
613
614
615local function checkcountersetup(name,level,start,state)
616 local noreset = true
617 counters.restart(name,1,start,noreset)
618 counters.setstate(name,state)
619 counters.setlevel(name,level)
620 sections.setchecker(name,level,counters.reset)
621end
622
623
624
625implement { name = "addcounter", actions = add, arguments = { "string", "integer", "integer" } }
626implement { name = "setcounter", actions = set, arguments = { "string", 1, "integer" } }
627implement { name = "setowncounter", actions = setown, arguments = { "string", 1, "string" } }
628implement { name = "restartcounter", actions = restart, arguments = { "string", 1, "integer" } }
629implement { name = "resetcounter", actions = reset, arguments = { "string", 1 } }
630implement { name = "incrementcounter", actions = add, arguments = { "string", 1, 1 } }
631implement { name = "decrementcounter", actions = add, arguments = { "string", 1, -1 } }
632
633implement { name = "setsubcounter", actions = set, arguments = { "string", "integer", "integer" } }
634implement { name = "setownsubcounter", actions = setown, arguments = { "string", "integer", "string" } }
635implement { name = "restartsubcounter", actions = restart, arguments = { "string", "integer", "integer" } }
636implement { name = "resetsubcounter", actions = reset, arguments = { "string", "integer" } }
637implement { name = "incrementsubcounter", actions = add, arguments = { "string", "integer", 1 } }
638implement { name = "decrementsubcounter", actions = add, arguments = { "string", "integer", -1 } }
639
640implement { name = "rawcountervalue", actions = { counters.raw , context }, arguments = { "string", 1 } }
641implement { name = "countervalue", actions = { counters.value , context }, arguments = { "string", 1 } }
642implement { name = "lastcountervalue", actions = { counters.last , context }, arguments = { "string", 1 } }
643implement { name = "firstcountervalue", actions = { counters.first , context }, arguments = { "string", 1 } }
644implement { name = "nextcountervalue", actions = { counters.next , context }, arguments = { "string", 1 } }
645implement { name = "previouscountervalue", actions = { counters.previous, context }, arguments = { "string", 1 } }
646implement { name = "subcountervalues", actions = { counters.subs , context }, arguments = { "string", 1 } }
647
648implement { name = "rawsubcountervalue", actions = { counters.raw , context }, arguments = { "string", "integer" } }
649implement { name = "subcountervalue", actions = { counters.value , context }, arguments = { "string", "integer" } }
650implement { name = "lastsubcountervalue", actions = { counters.last , context }, arguments = { "string", "integer" } }
651implement { name = "firstsubcountervalue", actions = { counters.first , context }, arguments = { "string", "integer" } }
652implement { name = "nextsubcountervalue", actions = { counters.next , context }, arguments = { "string", "integer" } }
653implement { name = "previoussubcountervalue", actions = { counters.previous, context }, arguments = { "string", "integer" } }
654implement { name = "subsubcountervalues", actions = { counters.subs , context }, arguments = { "string", "integer" } }
655
656implement { name = "savecounter", actions = counters.save, arguments = "string" }
657implement { name = "restorecounter", actions = counters.restore, arguments = "string" }
658
659implement { name = "incrementedcounter", actions = { add, context }, arguments = { "string", 1, 1 } }
660implement { name = "decrementedcounter", actions = { add, context }, arguments = { "string", 1, -1 } }
661
662implement { name = "showcounter", actions = showcounter, arguments = "string" }
663implement { name = "checkcountersetup", actions = checkcountersetup, arguments = { "string", "integer", "integer", "string" } }
664
665setmetatablecall(counterdata,function(t,k) return t[k] end)
666
667implement { name = "doifelsecounter", actions = { counterdata, commands.doifelse }, arguments = "string" }
668implement { name = "doifcounter", actions = { counterdata, commands.doif }, arguments = "string" }
669implement { name = "doifnotcounter", actions = { counterdata, commands.doifnot }, arguments = "string" }
670
671implement {
672 name = "definecounter",
673 actions = counters.define,
674 arguments = {
675 {
676 { "name" } ,
677 { "start", "integer" },
678 { "counter" },
679 { "method" },
680 }
681 }
682}
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738 |