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(target,name,level)
274 local cd = counterdata[name]
275 if cd then
276 local data = cd.data
277 local numbers = { }
278 local ownnumbers = { }
279 local depth = #data
280 if not level or level == 0 then
281 level = depth
282 elseif level > depth then
283 level = depth
284 end
285
286 for i=1,level do
287 local d = data[i]
288 if d then
289 local n = d.number
290 local o = d.own
291 if n ~= 0 then
292 numbers[i] = n
293 end
294 if o ~= "" then
295 ownnumbers[i] = o
296 end
297 end
298 end
299 target.numbers = numbers
300 if next(ownnumbers) then
301 target.ownnumbers = ownnumbers
302 end
303 end
304end
305
306
307
308function counters.previous(name,n)
309 return allocate(name,n).previous
310end
311
312function counters.next(name,n)
313 return allocate(name,n).next
314end
315
316counters.prev = counters.previous
317
318function counters.currentvalue(name,n)
319 return allocate(name,n).number
320end
321
322function counters.first(name,n)
323 return allocate(name,n).first
324end
325
326function counters.last(name,n)
327 return allocate(name,n).last
328end
329
330function counters.subs(name,n)
331 return counterdata[name].data[n].subs or 0
332end
333
334local function setvalue(name,tag,value)
335 local cd = counterdata[name]
336 if cd then
337 cd[tag] = value
338 end
339end
340
341counters.setvalue = setvalue
342
343function counters.setstate(name,value)
344 value = variables[value]
345 if value then
346 setvalue(name,"state",value)
347 end
348end
349
350function counters.setlevel(name,value)
351 setvalue(name,"level",value)
352end
353
354function counters.setoffset(name,value)
355 setvalue(name,"offset",value)
356end
357
358local function synchronize(name,d)
359 local dc = d.counter
360 if dc then
361 if trace_counters then
362 report_counters("action %a, name %a, counter %a, value %a","synchronize",name,dc,d.number)
363 end
364 texsetcount("global",dc,d.number)
365 end
366 local cs = counterspecials[name]
367 if cs then
368 if trace_counters then
369 report_counters("action %a, name %a, counter %a","synccommand",name,dc)
370 end
371 cs(name)
372 end
373end
374
375local function reset(name,n)
376 local cd = counterdata[name]
377 if cd then
378 local data = cd.data
379 for i=n or 1,#data do
380 local d = data[i]
381 savevalue(name,i)
382 local number = d.start or 0
383 d.number = number
384 d.own = nil
385 if trace_counters then
386 report_counters("action %a, name %a, sub %a, value %a","reset",name,i,number)
387 end
388 synchronize(name,d)
389 end
390 cd.numbers = nil
391 else
392 end
393end
394
395local function set(name,n,value)
396 local cd = counterdata[name]
397 if cd then
398 local d = allocate(name,n)
399 local number = value or 0
400 d.number = number
401 d.own = nil
402 if trace_counters then
403 report_counters("action %a, name %a, sub %a, value %a","set",name,"no",number)
404 end
405 synchronize(name,d)
406 end
407end
408
409local function check(name,data,start,stop)
410 for i=start or 1,stop or #data do
411 local d = data[i]
412 savevalue(name,i)
413 local number = d.start or 0
414 d.number = number
415 d.own = nil
416 if trace_counters then
417 report_counters("action %a, name %a, sub %a, value %a","check",name,i,number)
418 end
419 synchronize(name,d)
420 end
421end
422
423
424local function setown(name,n,value)
425 local cd = counterdata[name]
426 if cd then
427 local d = allocate(name,n)
428 d.own = value
429 d.number = (d.number or d.start or 0) + (d.step or 0)
430 local level = cd.level
431 if not level or level == -1 then
432
433 elseif level > 0 or level == -3 then
434 check(name,d,n+1)
435 elseif level == 0 then
436
437 end
438 synchronize(name,d)
439 end
440end
441
442local function restart(name,n,newstart,noreset)
443 local cd = counterdata[name]
444 if cd then
445 newstart = tonumber(newstart)
446 if newstart then
447 local d = allocate(name,n)
448 d.start = newstart
449 if not noreset then
450 reset(name,n)
451 end
452 end
453 end
454end
455
456function counters.save(name)
457 local cd = counterdata[name]
458 if cd then
459 insert(cd.saved,copy(cd.data))
460 end
461end
462
463function counters.restore(name)
464 local cd = counterdata[name]
465 if not cd then
466 report_counters("invalid restore, no counter %a",name)
467 return
468 end
469 local saved = cd.saved
470 if not saved then
471
472 elseif #saved > 0 then
473 cd.data = remove(saved)
474 else
475 report_counters("restore without save for counter %a",name)
476 end
477end
478
479local function add(name,n,delta)
480 local cd = counterdata[name]
481 if cd and (cd.state == v_start or cd.state == "") then
482 local data = cd.data
483 local d = allocate(name,n)
484 d.number = (d.number or d.start or 0) + delta*(d.step or 0)
485
486 local level = cd.level
487 if not level or level == -1 then
488
489 if trace_counters then
490 report_counters("action %a, name %a, sub %a, how %a","add",name,"no","no checking")
491 end
492 elseif level == -2 then
493
494 if trace_counters then
495 report_counters("action %a, name %a, sub %a, how %a","add",name,"text","checking")
496 end
497 check(name,data,n+1)
498 elseif level > 0 or level == -3 then
499
500 if trace_counters then
501 report_counters("action %a, name %a, sub %a, how %a","add",name,level,"checking within group")
502 end
503 check(name,data,n+1)
504 elseif level == 0 then
505
506 if trace_counters then
507 report_counters("action %a, name %a, sub %a, how %a","add",name,level,"no checking")
508 end
509 else
510 if trace_counters then
511 report_counters("action %a, name %a, sub %a, how %a","add",name,"unknown","no checking")
512 end
513 end
514 synchronize(name,d)
515 return d.number
516 end
517 return 0
518end
519
520function counters.check(level)
521 for name, cd in next, counterdata do
522 if level > 0 and cd.level == -3 then
523 if trace_counters then
524 report_counters("action %a, name %a, sub %a, detail %a","reset",name,level,"head")
525 end
526 reset(name)
527 elseif cd.level == level then
528 if trace_counters then
529 report_counters("action %a, name %a, sub %a, detail %a","reset",name,level,"normal")
530 end
531 reset(name)
532 end
533 end
534end
535
536local function get(name,n,key)
537 local d = allocate(name,n)
538 d = d and d[key]
539 if not d then
540 return 0
541 elseif type(d) == "function" then
542 return d()
543 else
544 return d
545 end
546end
547
548counters.reset = reset
549counters.set = set
550counters.add = add
551counters.get = get
552counters.setown = setown
553counters.restart = restart
554
555function counters.value(name,n)
556 return get(name,n or 1,'number') or 0
557end
558
559function counters.converted(name,spec)
560 local cd
561 if type(name) == "number" then
562 cd = specials.retrieve("counter",name)
563 if cd then
564 cd = cd.counter
565 end
566 else
567 cd = counterdata[name]
568 end
569 if cd then
570 local spec = spec or { }
571 local numbers = { }
572 local ownnumbers = { }
573 local reverse = spec.order == v_reverse
574 local kind = spec.type or "number"
575 local data = cd.data
576 for k=1,#data do
577 local v = data[k]
578
579 local vn
580 if v.own then
581 numbers[k] = v.number
582 ownnumbers[k] = v.own
583 else
584 if kind == v_first then
585 vn = v.first
586 elseif kind == v_next then
587 vn = v.next
588 elseif kind == v_prev or kind == v_previous then
589 vn = v.prev
590 elseif kind == v_last then
591 vn = v.last
592 else
593 vn = v.number
594 if reverse then
595 local vf = v.first
596 local vl = v.last
597 if vl > 0 then
598
599 vn = vl - vn + vf
600 end
601 end
602 end
603 numbers[k] = vn or v.number
604 ownnumbers[k] = nil
605 end
606 end
607 cd.numbers = numbers
608 cd.ownnumbers = ownnumbers
609 sections.typesetnumber(cd,'number',spec)
610 cd.numbers = nil
611 cd.ownnumbers = nil
612 end
613end
614
615
616
617local function showcounter(name)
618 local cd = counterdata[name]
619 if cd then
620 context("[%s:",name)
621 local data = cd.data
622 for i=1,#data do
623 local d = data[i]
624 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)
625 end
626 context("]")
627 end
628end
629
630
631
632
633
634local function checkcountersetup(name,level,start,state)
635 local noreset = true
636 counters.restart(name,1,start,noreset)
637 counters.setstate(name,state)
638 counters.setlevel(name,level)
639 sections.setchecker(name,level,counters.reset)
640end
641
642
643
644implement { name = "addcounter", actions = add, arguments = { "string", "integer", "integer" } }
645implement { name = "setcounter", actions = set, arguments = { "string", 1, "integer" } }
646implement { name = "setowncounter", actions = setown, arguments = { "string", 1, "string" } }
647implement { name = "restartcounter", actions = restart, arguments = { "string", 1, "integer" } }
648implement { name = "resetcounter", actions = reset, arguments = { "string", 1 } }
649implement { name = "incrementcounter", actions = add, arguments = { "string", 1, 1 } }
650implement { name = "decrementcounter", actions = add, arguments = { "string", 1, -1 } }
651
652implement { name = "setsubcounter", actions = set, arguments = { "string", "integer", "integer" } }
653implement { name = "setownsubcounter", actions = setown, arguments = { "string", "integer", "string" } }
654implement { name = "restartsubcounter", actions = restart, arguments = { "string", "integer", "integer" } }
655implement { name = "resetsubcounter", actions = reset, arguments = { "string", "integer" } }
656implement { name = "incrementsubcounter", actions = add, arguments = { "string", "integer", 1 } }
657implement { name = "decrementsubcounter", actions = add, arguments = { "string", "integer", -1 } }
658
659implement { name = "rawcountervalue", actions = { counters.raw , context }, arguments = { "string", 1 } }
660implement { name = "countervalue", actions = { counters.value , context }, arguments = { "string", 1 } }
661implement { name = "lastcountervalue", actions = { counters.last , context }, arguments = { "string", 1 } }
662implement { name = "firstcountervalue", actions = { counters.first , context }, arguments = { "string", 1 } }
663implement { name = "nextcountervalue", actions = { counters.next , context }, arguments = { "string", 1 } }
664implement { name = "previouscountervalue", actions = { counters.previous, context }, arguments = { "string", 1 } }
665implement { name = "subcountervalues", actions = { counters.subs , context }, arguments = { "string", 1 } }
666
667implement { name = "rawsubcountervalue", actions = { counters.raw , context }, arguments = { "string", "integer" } }
668implement { name = "subcountervalue", actions = { counters.value , context }, arguments = { "string", "integer" } }
669implement { name = "lastsubcountervalue", actions = { counters.last , context }, arguments = { "string", "integer" } }
670implement { name = "firstsubcountervalue", actions = { counters.first , context }, arguments = { "string", "integer" } }
671implement { name = "nextsubcountervalue", actions = { counters.next , context }, arguments = { "string", "integer" } }
672implement { name = "previoussubcountervalue", actions = { counters.previous, context }, arguments = { "string", "integer" } }
673implement { name = "subsubcountervalues", actions = { counters.subs , context }, arguments = { "string", "integer" } }
674
675implement { name = "savecounter", actions = counters.save, arguments = "string" }
676implement { name = "restorecounter", actions = counters.restore, arguments = "string" }
677
678implement { name = "incrementedcounter", actions = { add, context }, arguments = { "string", 1, 1 } }
679implement { name = "decrementedcounter", actions = { add, context }, arguments = { "string", 1, -1 } }
680
681implement { name = "showcounter", actions = showcounter, arguments = "string" }
682implement { name = "checkcountersetup", actions = checkcountersetup, arguments = { "string", "integer", "integer", "string" } }
683
684setmetatablecall(counterdata,function(t,k) return t[k] end)
685
686implement { name = "doifelsecounter", actions = { counterdata, commands.doifelse }, arguments = "string" }
687implement { name = "doifcounter", actions = { counterdata, commands.doif }, arguments = "string" }
688implement { name = "doifnotcounter", actions = { counterdata, commands.doifnot }, arguments = "string" }
689
690implement {
691 name = "definecounter",
692 actions = counters.define,
693 arguments = {
694 {
695 { "name" } ,
696 { "start", "integer" },
697 { "counter" },
698 { "method" },
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
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757 |