core-con.lua /size: 68 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['core-con'] = {
2    version   = 1.001,
3    comment   = "companion to core-con.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
9-- todo: split into lang-con.lua and core-con.lua
10
11--[[ldx--
12<p>This module implements a bunch of conversions. Some are more
13efficient than their <l n='tex'/> counterpart, some are even
14slower but look nicer this way.</p>
15
16<p>Some code may move to a module in the language namespace.</p>
17--ldx]]--
18
19local floor = math.floor
20local osdate, ostime, ostimezone = os.date, os.time, os.timezone
21local concat, insert, reverse = table.concat, table.insert, table.reverse
22local lower, upper, rep, match, gsub = string.lower, string.upper, string.rep, string.match, string.gsub
23local utfchar, utfbyte = utf.char, utf.byte
24local tonumber, tostring, type, rawset = tonumber, tostring, type, rawset
25local P, S, R, Cc, Cf, Cg, Ct, Cs, C, V, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.Cs, lpeg.C, lpeg.V, lpeg.Carg
26local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
27local div, mod = math.div, math.mod
28
29local context            = context
30local commands           = commands
31local implement          = interfaces.implement
32
33local settings_to_array  = utilities.parsers.settings_to_array
34local allocate           = utilities.storage.allocate
35local setmetatableindex  = table.setmetatableindex
36local formatters         = string.formatters
37local variables          = interfaces.variables
38local constants          = interfaces.constants
39local addformatter       = utilities.strings.formatters.add
40
41local texset             = tex.set
42
43converters               = converters or { }
44local converters         = converters
45
46languages                = languages  or { }
47local languages          = languages
48
49local helpers            = converters.helpers or { }
50converters.helpers       = helpers
51
52local ctx_labeltext      = context.labeltext
53local ctx_LABELTEXT      = context.LABELTEXT
54local ctx_space          = context.space
55local ctx_convertnumber  = context.convertnumber
56local ctx_highordinalstr = context.highordinalstr
57
58converters.number  = tonumber
59converters.numbers = tonumber
60
61implement { name = "number",  actions = context }
62implement { name = "numbers", actions = context }
63
64-- to be reconsidered ... languages namespace here, might become local plus a register command
65
66local counters = allocate {
67    ['default'] = { -- no metatable as we do a test on keys
68        0x0061, 0x0062, 0x0063, 0x0064, 0x0065,
69        0x0066, 0x0067, 0x0068, 0x0069, 0x006A,
70        0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
71        0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
72        0x0075, 0x0076, 0x0077, 0x0078, 0x0079,
73        0x007A
74    },
75    ['slovenian'] = {
76        0x0061, 0x0062, 0x0063, 0x010D, 0x0064,
77        0x0065, 0x0066, 0x0067, 0x0068, 0x0069,
78        0x006A, 0x006B, 0x006C, 0x006D, 0x006E,
79        0x006F, 0x0070, 0x0072, 0x0073, 0x0161,
80        0x0074, 0x0075, 0x0076, 0x007A, 0x017E
81    },
82    ['spanish'] = {
83        0x0061, 0x0062, 0x0063, 0x0064, 0x0065,
84        0x0066, 0x0067, 0x0068, 0x0069, 0x006A,
85        0x006B, 0x006C, 0x006D, 0x006E, 0x00F1,
86        0x006F, 0x0070, 0x0071, 0x0072, 0x0073,
87        0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
88        0x0079, 0x007A
89    },
90    ['russian'] = {
91        0x0430, 0x0431, 0x0432, 0x0433, 0x0434,
92        0x0435, 0x0436, 0x0437, 0x0438, 0x043a,
93        0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
94        0x0440, 0x0441, 0x0442, 0x0443, 0x0444,
95        0x0445, 0x0446, 0x0447, 0x0448, 0x0449,
96        0x044d, 0x044e, 0x044f
97    },
98    ['greek'] = { -- this should be the lowercase table
99     -- 0x0391, 0x0392, 0x0393, 0x0394, 0x0395,
100     -- 0x0396, 0x0397, 0x0398, 0x0399, 0x039A,
101     -- 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
102     -- 0x03A0, 0x03A1, 0x03A3, 0x03A4, 0x03A5,
103     -- 0x03A6, 0x03A7, 0x03A8, 0x03A9
104        0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5,
105        0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA,
106        0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
107        0x03C0, 0x03C1, 0x03C3, 0x03C4, 0x03C5,
108        0x03C6, 0x03C7, 0x03C8, 0x03C9,
109    },
110    ['arabic'] = {
111        0x0627, 0x0628, 0x062C, 0x062F, 0x0647,
112        0x0648, 0x0632, 0x062D, 0x0637, 0x0649,
113        0x0643, 0x0644, 0x0645, 0x0646, 0x0633,
114        0x0639, 0x0641, 0x0635, 0x0642, 0x0631,
115        0x0634, 0x062A, 0x062B, 0x062E, 0x0630,
116        0x0636, 0x0638, 0x063A,
117    },
118    ['persian'] = {
119        0x0627, 0x0628, 0x062C, 0x062F, 0x0647,
120        0x0648, 0x0632, 0x062D, 0x0637, 0x0649,
121        0x06A9, 0x0644, 0x0645, 0x0646, 0x0633,
122        0x0639, 0x0641, 0x0635, 0x0642, 0x0631,
123        0x0634, 0x062A, 0x062B, 0x062E, 0x0630,
124        0x0636, 0x0638, 0x063A,
125    },
126    ['thai'] = {
127        0xE050, 0xE051, 0xE052, 0xE053, 0xE054,
128        0xE055, 0xE056, 0xE057, 0xE058, 0xE059
129    },
130    ['devangari'] = {
131        0x0966, 0x0967, 0x0968, 0x0969, 0x096A,
132        0x096B, 0x096C, 0x096D, 0x096E, 0x096F
133    },
134    ['gurmurkhi'] = {
135        0x0A66, 0x0A67, 0x0A68, 0x0A69, 0x0A6A,
136        0x0A6B, 0x0A6C, 0x0A6D, 0x0A6E, 0x0A6F
137    },
138    ['gujarati'] = {
139        0x0AE6, 0x0AE7, 0x0AE8, 0x0AE9, 0x0AEA,
140        0x0AEB, 0x0AEC, 0x0AED, 0x0AEE, 0x0AEF
141    },
142    ['tibetan'] = {
143        0x0F20, 0x0F21, 0x0F22, 0x0F23, 0x0F24,
144        0x0F25, 0x0F26, 0x0F27, 0x0F28, 0x0F29
145    },
146    ['korean'] = {
147        0x3131, 0x3134, 0x3137, 0x3139, 0x3141,
148        0x3142, 0x3145, 0x3147, 0x3148, 0x314A,
149        0x314B, 0x314C, 0x314D, 0x314E
150    },
151    ['korean-parenthesis'] = { --
152        0x3200, 0x3201, 0x3202, 0x3203, 0x3204,
153        0x3205, 0x3206, 0x3207, 0x3208, 0x3209,
154        0x320A, 0x320B, 0x320C, 0x320D
155    },
156    ['korean-circle'] = { -- circled
157        0x3260, 0x3261, 0x3262, 0x3263, 0x3264,
158        0x3265, 0x3266, 0x3267, 0x3268, 0x3269,
159        0x326A, 0x326B, 0x326C, 0x326D
160    },
161}
162
163languages.counters = counters
164
165counters['ar']                        = counters['arabic']
166counters['gr']                        = counters['greek']
167counters['g']                         = counters['greek']
168counters['sl']                        = counters['slovenian']
169counters['es']                        = counters['spanish']
170counters['ru']                        = counters['russian']
171counters['kr']                        = counters['korean']
172counters['kr-p']                      = counters['korean-parenthesis']
173counters['kr-c']                      = counters['korean-circle']
174
175counters['thainumerals']              = counters['thai']
176counters['devanagarinumerals']        = counters['devanagari']
177counters['gurmurkhinumerals']         = counters['gurmurkhi']
178counters['gujaratinumerals']          = counters['gujarati']
179counters['tibetannumerals']           = counters['tibetan']
180counters['greeknumerals']             = counters['greek']
181counters['arabicnumerals']            = counters['arabic']
182counters['persiannumerals']           = counters['persian']
183counters['arabicexnumerals']          = counters['persian']
184counters['koreannumerals']            = counters['korean']
185counters['koreanparenthesisnumerals'] = counters['korean-parenthesis']
186counters['koreancirclenumerals']      = counters['korean-circle']
187
188counters['sloveniannumerals']         = counters['slovenian']
189counters['spanishnumerals']           = counters['spanish']
190counters['russiannumerals']           = counters['russian']
191
192local decimals = allocate {
193    ['arabic'] = {
194        ["0"] = "٠", ["1"] = "١", ["2"] = "٢", ["3"] = "٣", ["4"] = "٤",
195        ["5"] = "٥", ["6"] = "٦", ["7"] = "٧", ["8"] = "٨", ["9"] = "٩",
196    },
197    ['persian'] = {
198        ["0"] = "۰", ["1"] = "۱", ["2"] = "۲", ["3"] = "۳", ["4"] = "۴",
199        ["5"] = "۵", ["6"] = "۶", ["7"] = "۷", ["8"] = "۸", ["9"] = "۹",
200    }
201}
202
203languages.decimals = decimals
204
205local fallback = utfbyte('0')
206
207local function chr(n,m)
208    return (n > 0 and n < 27 and utfchar(n+m)) or ""
209end
210
211local function chrs(n,m,t)
212    if not t then
213        t = { }
214    end
215    if n > 26 then
216        chrs(floor((n-1)/26),m,t)
217        n = (n-1)%26 + 1
218    end
219    if n ~= 0 then
220        t[#t+1] = utfchar(n+m)
221    end
222    if n <= 26 then
223        return concat(t)
224    end
225end
226
227local function maxchrs(n,m,cmd,t)
228    if not t then
229        t = { }
230    end
231    if n > m then
232        maxchrs(floor((n-1)/m),m,cmd)
233        n = (n-1)%m + 1
234    end
235    t[#t+1] = formatters["%s{%s}"](cmd,n)
236    if n <= m then
237        return concat(t)
238    end
239end
240
241converters.chr     = chr
242converters.chrs    = chrs
243converters.maxchrs = maxchrs
244
245local lowercharacter = characters.lcchars
246local uppercharacter = characters.ucchars
247
248local defaultcounter = counters.default
249
250local function do_alphabetic(n,mapping,mapper,t) -- todo: make zero based variant (initial n + 1)
251    if not t then
252        t = { }
253    end
254    local max = #mapping
255    if n > max then
256        do_alphabetic(floor((n-1)/max),mapping,mapper,t)
257        n = (n-1) % max + 1
258    end
259    local chr = mapping[n] or fallback
260    t[#t+1] = mapper and mapper[chr] or chr
261    if n <= max then
262        return concat(t)
263    end
264end
265
266local function alphabetic(n,code)
267    return do_alphabetic(n,code and code ~= "" and counters[code] or defaultcounter,lowercharacter)
268end
269
270local function Alphabetic(n,code)
271    return do_alphabetic(n,code and code ~= "" and counters[code] or defaultcounter,uppercharacter)
272end
273
274converters.alphabetic = alphabetic
275converters.Alphabetic = Alphabetic
276
277-- we could make a replacer
278
279local function todecimals(n,name)
280    local stream  = tostring(n)
281    local mapping = decimals[name]
282    return mapping and gsub(stream,".",mapping) or stream
283end
284
285converters.decimals = todecimals
286
287local lower_offset = 96
288local upper_offset = 64
289
290function converters.character (n) return chr (n,lower_offset) end
291function converters.Character (n) return chr (n,upper_offset) end
292function converters.characters(n) return chrs(n,lower_offset) end
293function converters.Characters(n) return chrs(n,upper_offset) end
294
295implement { name = "alphabetic", actions = { alphabetic, context }, arguments = { "integer", "string" } }
296implement { name = "Alphabetic", actions = { Alphabetic, context }, arguments = { "integer", "string" } }
297
298implement { name = "character",  actions = { chr,  context }, arguments = { "integer", lower_offset } }
299implement { name = "Character",  actions = { chr,  context }, arguments = { "integer", upper_offset } }
300implement { name = "characters", actions = { chrs, context }, arguments = { "integer", lower_offset } }
301implement { name = "Characters", actions = { chrs, context }, arguments = { "integer", upper_offset } }
302
303implement { name = "decimals",   actions = { todecimals, context }, arguments = { "integer", "string" } }
304
305local weekday    = os.weekday    -- moved to l-os
306local isleapyear = os.isleapyear -- moved to l-os
307local nofdays    = os.nofdays    -- moved to l-os
308
309local function leapyear(year)
310    return isleapyear(year) and 1 or 0
311end
312
313local function textime()
314    return tonumber(osdate("%H")) * 60 + tonumber(osdate("%M"))
315end
316
317-- For consistenty we need to add day here but that conflicts with the current
318-- serializer so then best is to have none from now on:
319
320-- function converters.year  () return osdate("%Y") end
321-- function converters.month () return osdate("%m") end -- always two digits
322-- function converters.day   () return osdate("%d") end -- conflicts
323-- function converters.hour  () return osdate("%H") end
324-- function converters.minute() return osdate("%M") end
325-- function converters.second() return osdate("%S") end
326
327converters.weekday    = weekday
328converters.isleapyear = isleapyear
329converters.leapyear   = leapyear
330converters.nofdays    = nofdays
331converters.textime    = textime
332
333implement { name = "weekday",  actions = { weekday,  context }, arguments = { "integer", "integer", "integer" } }
334implement { name = "leapyear", actions = { leapyear, context }, arguments = "integer" }
335implement { name = "nofdays",  actions = { nofdays,  context }, arguments = { "integer", "integer" } }
336
337implement { name = "year",     actions = { osdate,   context }, arguments = "'%Y'" }
338implement { name = "month",    actions = { osdate,   context }, arguments = "'%m'" }
339implement { name = "day",      actions = { osdate,   context }, arguments = "'%d'" }
340implement { name = "hour",     actions = { osdate,   context }, arguments = "'%H'" }
341implement { name = "minute",   actions = { osdate,   context }, arguments = "'%M'" }
342implement { name = "second",   actions = { osdate,   context }, arguments = "'%S'" }
343implement { name = "textime",  actions = { textime,  context } }
344
345implement {
346    name      = "doifelseleapyear",
347    actions   = { isleapyear, commands.doifelse },
348    arguments = "integer"
349}
350
351local roman = {
352    { [0] = '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' },
353    { [0] = '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC' },
354    { [0] = '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM' },
355}
356
357local function toroman(n)
358    if n >= 4000 then
359        return toroman(floor(n/1000)) .. " " .. toroman(n%1000)
360    else
361        return rep("M",floor(n/1000)) .. roman[3][floor((n%1000)/100)] .. roman[2][floor((n%100)/10)] .. roman[1][floor((n%10)/1)]
362    end
363end
364
365converters.toroman       = toroman
366converters.Romannumerals = toroman
367converters.romannumerals = function(n) return lower(toroman(n)) end
368
369converters['i']  = converters.romannumerals
370converters['I']  = converters.Romannumerals
371converters['r']  = converters.romannumerals
372converters['R']  = converters.Romannumerals
373converters['KR'] = converters.Romannumerals
374converters['RK'] = converters.Romannumerals
375
376implement {
377    name      = "romannumerals",
378    actions   = { toroman, lower, context },
379    arguments = "integer",
380}
381
382implement {
383    name      = "Romannumerals",
384    actions   = { toroman, context },
385    arguments = "integer",
386}
387
388--~ local small = {
389--~     0x0627, 0x066E, 0x062D, 0x062F, 0x0647, 0x0648, 0x0631
390--~ }
391
392--~ local large = {
393--~     { 0x0627, 0x0628, 0x062C, 0x062F, 0x0647, 0x0648, 0x0632, 0x062D, 0x0637, },
394--~     { 0x064A, 0x0643, 0x0644, 0x0645, 0x0646, 0x0633, 0x0639, 0x0641, 0x0635, },
395--~     { 0x0642, 0x0631, 0x0634, 0x062A, 0x062B, 0x062E, 0x0630, 0x0636, 0x0638, },
396--~     { 0x063A                                                                  },
397--~ }
398
399local small = {
400    "ا", "ٮ", "ح", "د", "ه", "و", "ر",
401}
402
403local medium = {
404     "ا", "ب", "ج", "د", "ه", "و","ز", "ح", "ط" ,
405     "ي", "ك", "ل", "م", "ن", "س", "ع", "ف", "ص" ,
406     "ق", "ر", "ش", "ت", "ث", "خ", "ذ", "ض", "ظ" ,
407     "غ" ,
408}
409
410local large = {
411    { "ا", "ب", "ج", "د", "ه", "و","ز", "ح", "ط" },
412    { "ي", "ك", "ل", "م", "ن", "س", "ع", "ف", "ص" },
413    { "ق", "ر", "ش", "ت", "ث", "خ", "ذ", "ض", "ظ" },
414    { "غ" },
415}
416
417local function toabjad(n,what)
418    if n <= 0 or n >= 2000 then
419        return tostring(n)
420    elseif what == 2 and n <= 7 then
421        return small[n]
422    elseif what == 3 and n <= 28 then
423        return medium[n]
424    else
425        local a, b, c, d
426        a, n = floor(n/1000), n % 1000 -- mod(n,1000)
427        b, n = floor(n/ 100), n %  100 -- mod(n, 100)
428        c, n = floor(n/  10), n %   10 -- mod(n,  10)
429        d, n = floor(n/   1), n %    1 -- mod(n,   1)
430        return (large[4][a] or "") .. (large[3][b] or "") .. (large[2][c] or "") .. (large[1][d] or "")
431    end
432end
433
434converters.toabjad = toabjad
435
436function converters.abjadnumerals     (n) return toabjad(n,false) end
437function converters.abjadnodotnumerals(n) return toabjad(n,true ) end
438
439implement {
440    name      = "abjadnumerals",
441    actions   = { toabjad, context },
442    arguments = { "integer", false }
443}
444
445implement {
446    name      = "abjadnodotnumerals",
447    actions   = { toabjad, context },
448    arguments = { "integer", true }
449}
450
451-- -- - hebrew and jiddish -- -- --
452
453local trace_hebrew  trackers.register("converters.hebrew", function(v)
454    trace_hebrew = v
455end)
456
457local list = {
458    { 400, "ת" }, { 300, "ש" }, { 200, "ר" }, { 100, "ק" },
459    {  90, "צ" }, { 80, "פ" }, { 70, "ע" }, { 60, "ס "}, { 50, "נ" }, { 40, "מ" }, { 30, "ל" }, { 20, "כ" }, { 10, "י" },
460    {   9, "ט" }, { 8, "ח" }, { 7, "ז", }, { 6, "ו", }, { 5, "ה" }, { 4, "ד" }, { 3, "ג" }, { 2, "ב" }, { 1, "א" },
461}
462
463local special = {
464    [15] = "ט״ו", -- exception: avoid mixup with God יה
465    [16] = "ט״ז", -- exception: avoid mixup with God יו
466}
467
468local function tohebrew(n,gershayim,geresh)
469    local split = { }
470    local size  = 0
471    while n > 1000 do
472        size = size + 1
473        split[size] = n % 1000
474        n = floor(n/1000)
475    end
476    size = size + 1
477    split[size] = n
478    for i=1,size do
479        local t = { }
480        local n = 0
481        local s = split[i]
482        while s > 0 do
483            for i=1,#list do
484              ::again::
485                local li = list[i]
486                local l1 = li[1]
487                local s1 = special[l1]
488                if s1 then
489                    s = s - l1
490                    n = n + 1
491                    t[n] = s1
492                    goto again
493                elseif s >= l1 then
494                    s = s - l1
495                    n = n + 1
496                    t[n] = li[2]
497                    goto again
498                end
499            end
500        end
501        ::done::
502        split[i] = t
503    end
504    if gershayim then
505        for i=1,size do
506            local si = split[i]
507            local ni = #si
508            if ni >= 2 then
509                local s = "״"
510                insert(split[i],ni,trace_hebrew and ("{\\red "..s.."}") or s)
511            end
512        end
513    end
514    if geresh then
515        for i=2,#split do
516            local s = rep("׳",i-1)
517            insert(split[i],trace_hebrew and ("{\\blue "..s.."}") or s)
518        end
519    end
520    for i=1,size do
521        split[i] = concat(split[i])
522    end
523    return concat(reverse(split))
524end
525
526converters.tohebrew       = tohebrew
527converters.hebrewnumerals = converters.tohebrew
528
529-- converters['alphabetic:hb'] = converters.hebrewnumerals
530
531interfaces.implement {
532    name      = "hebrewnumerals",
533    actions   = { tohebrew, context },
534    arguments = { "integer", true, true }
535}
536
537-- -- --
538
539local vector = {
540    normal = {
541                [0] = "",
542                [1] = "",
543                [2] = "",
544                [3] = "",
545                [4] = "",
546                [5] = "",
547                [6] = "",
548                [7] = "",
549                [8] = "",
550                [9] = "",
551               [10] = "",
552              [100] = "",
553             [1000] = "",
554            [10000] = "",
555        [100000000] = "亿",
556    },
557    cap = {
558                [0] = "",
559                [1] = "",
560                [2] = "",
561                [3] = "",
562                [4] = "",
563                [5] = "",
564                [6] = "",
565                [7] = "",
566                [8] = "",
567                [9] = "",
568               [10] = "",
569              [100] = "",
570             [1000] = "",
571            [10000] = "",
572        [100000000] = "亿",
573    },
574    all = {
575                [0] = "",
576                [1] = "",
577                [2] = "",
578                [3] = "",
579                [4] = "",
580                [5] = "",
581                [6] = "",
582                [7] = "",
583                [8] = "",
584                [9] = "",
585               [10] = "",
586               [20] = "廿",
587               [30] = "",
588              [100] = "",
589             [1000] = "",
590            [10000] = "",
591        [100000000] = "亿",
592    }
593}
594
595local function tochinese(n,name) -- normal, caps, all
596 -- improved version by Li Yanrui
597    local result, r = { }, 0
598    local vector = vector[name] or vector.normal
599    while true do
600        if n == 0 then
601            break
602        elseif n >= 100000000 then
603            local m = floor(n/100000000)
604            r = r + 1 ; result[r] = tochinese(m,name)
605            r = r + 1 ; result[r] = vector[100000000]
606            local z = n - m * 100000000
607            if z > 0 and z < 10000000 then r = r + 1 ; result[r] = vector[0] end
608            n = n % 100000000
609        elseif n >= 10000000 then
610            local m = floor(n/10000)
611            r = r + 1 ; result[r] = tochinese(m,name)
612            r = r + 1 ; result[r] = vector[10000]
613            local z = n - m * 10000
614            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
615            n = n % 10000
616        elseif n >= 1000000 then
617            local m = floor(n/10000)
618            r = r + 1 ; result[r] = tochinese(m,name)
619            r = r + 1 ; result[r] = vector[10000]
620            local z = n - m * 10000
621            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
622            n = n % 10000
623        elseif n >= 100000 then
624            local m = floor(n/10000)
625            r = r + 1 ; result[r] = tochinese(m,name)
626            r = r + 1 ; result[r] = vector[10000]
627            local z = n - m * 10000
628            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
629            n = n % 10000
630         elseif n >= 10000 then
631            local m = floor(n/10000)
632            r = r + 1 ; result[r] = vector[m]
633            r = r + 1 ; result[r] = vector[10000]
634            local z = n - m * 10000
635            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
636            n = n % 10000
637         elseif n >= 1000 then
638            local m = floor(n/1000)
639            r = r + 1 ; result[r] = vector[m]
640            r = r + 1 ; result[r] = vector[1000]
641            local z =  n - m * 1000
642            if z > 0 and z < 100 then r = r + 1 ; result[r] = vector[0] end
643            n = n % 1000
644         elseif n >= 100 then
645            local m = floor(n/100)
646            r = r + 1 ; result[r] = vector[m]
647            r = r + 1 ; result[r] = vector[100]
648            local z = n - m * 100
649            if z > 0 and z < 10 then r = r + 1 ; result[r] = vector[0] end
650            n = n % 100
651         elseif n >= 10 then
652            local m = floor(n/10)
653            if m > 1 and vector[m*10] then
654                r = r + 1 ; result[r] = vector[m*10]
655            else
656                r = r + 1 ; result[r] = vector[m]
657                r = r + 1 ; result[r] = vector[10]
658            end
659            n = n % 10
660        else
661            r = r + 1 ; result[r] = vector[n]
662            break
663        end
664    end
665    if (result[1] == vector[1] and result[2] == vector[10]) then
666        result[1] = ""
667    end
668    return concat(result)
669end
670
671-- local t = { 1,10,15,25,35,45,11,100,111,1111,10000,11111,100000,111111,1111111,11111111,111111111,100000000,1111111111,11111111111,111111111111,1111111111111 }
672-- for k=1,#t do
673-- local v = t[k]
674--     print(v,tochinese(v),tochinese(v,"all"),tochinese(v,"cap"))
675-- end
676
677converters.tochinese = tochinese
678
679function converters.chinesenumerals   (n,how) return tochinese(n,how or "normal") end
680function converters.chinesecapnumerals(n)     return tochinese(n,"cap") end
681function converters.chineseallnumerals(n)     return tochinese(n,"all") end
682
683converters['cn']   = converters.chinesenumerals
684converters['cn-c'] = converters.chinesecapnumerals
685converters['cn-a'] = converters.chineseallnumerals
686
687implement {
688    name      = "chinesenumerals",
689    actions   = { tochinese, context },
690    arguments = { "integer", "string" }
691}
692
693-- this is a temporary solution: we need a better solution when we have
694-- more languages
695
696converters['a']  = converters.characters
697converters['A']  = converters.Characters
698converters['AK'] = converters.Characters -- obsolete
699converters['KA'] = converters.Characters -- obsolete
700
701function converters.spanishnumerals  (n) return alphabetic(n,"es") end
702function converters.Spanishnumerals  (n) return Alphabetic(n,"es") end
703function converters.sloveniannumerals(n) return alphabetic(n,"sl") end
704function converters.Sloveniannumerals(n) return Alphabetic(n,"sl") end
705function converters.russiannumerals  (n) return alphabetic(n,"ru") end
706function converters.Russiannumerals  (n) return Alphabetic(n,"ru") end
707
708converters['alphabetic:es'] = converters.spanishnumerals
709converters['alphabetic:sl'] = converters.sloveniannumerals
710converters['alphabetic:ru'] = converters.russiannumerals
711
712converters['Alphabetic:es'] = converters.Spanishnumerals
713converters['Alphabetic:sl'] = converters.Sloveniannumerals
714converters['Alphabetic:ru'] = converters.Russiannumerals
715
716-- bonus
717
718converters['a:es']  = converters.spanishnumerals
719converters['a:sl']  = converters.sloveniannumerals
720converters['a:ru']  = converters.russiannumerals
721converters['A:es']  = converters.Spanishnumerals
722converters['A:sl']  = converters.Sloveniannumerals
723converters['A:ru']  = converters.Russiannumerals
724
725-- end of bonus
726
727converters.sequences = converters.sequences or { }
728local sequences      = converters.sequences
729
730storage.register("converters/sequences", sequences, "converters.sequences")
731
732function converters.define(name,set) -- ,language)
733 -- if language then
734 --     name = name .. ":" .. language
735 -- end
736    sequences[name] = settings_to_array(set)
737end
738
739function converters.max(name)
740    local s = sequences[name]
741    return s and #s or 0
742end
743
744implement {
745    name      = "defineconversion",
746    actions   = converters.define,
747    arguments = "2 strings",
748}
749
750implement {
751    name      = "nofconversions",
752    actions   = { converters.max, context },
753    arguments = "string",
754}
755
756local function convert(method,n,language)
757    local converter = language and converters[method..":"..language] or converters[method]
758    if converter then
759        return converter(n)
760    else
761        local lowermethod = lower(method)
762        local linguistic  = counters[lowermethod]
763        if linguistic then
764            return do_alphabetic(n,linguistic,lowermethod == method and lowercharacter or uppercharacter)
765        end
766        local sequence = sequences[method]
767        if sequence then
768            local max = #sequence
769            if n > max then
770                return sequence[(n-1) % max + 1]
771            else
772                return sequence[n]
773            end
774        end
775        return n
776    end
777end
778
779converters.convert = convert
780
781local function valid(method,language)
782    return converters[method..":"..language] or converters[method] or sequences[method]
783end
784
785implement {
786    name      = "doifelseconverter",
787    actions   = { valid, commands.doifelse },
788    arguments = "2 strings",
789}
790
791implement {
792    name      = "checkedconversion",
793    actions   = { convert, context },
794    arguments = { "string", "integer" }
795}
796
797-- hebrew data conversion
798
799local gregorian_to_hebrew  do
800
801    -- The next code is based on the c code at https://github.com/hebcal/hebcal
802    --
803    -- Hebcal     : A Jewish Calendar Generator
804    -- Copyright  : 1994 - 2004 Danny Sadinoff, 2002 Michael J. Radwin
805    -- License    : GPL
806    --
807    -- Because we only need simple dates, we use part of the code. There is more
808    -- at that github place.
809    --
810    -- The number of days elapsed between the Gregorian date 12/31/1 BC and DATE.
811    -- The Gregorian date Sunday, December 31, 1 BC is imaginary.
812    --
813    -- The code is luafied and a little different because we have no assignments
814    -- in loops and such. We could speed up the code but then we divert from the
815    -- original. This is not that critical anyway.
816
817    local NISAN    =  1
818    local IYYAR    =  2
819    local SIVAN    =  3
820    local TAMUZ    =  4
821    local AV       =  5
822    local ELUL     =  6
823    local TISHREI  =  7
824    local CHESHVAN =  8
825    local KISLEV   =  9
826    local TEVET    = 10
827    local SHVAT    = 11
828    local ADAR_I   = 12
829    local ADAR_II  = 13
830
831    local mmap = {
832        KISLEV,
833        TEVET,
834        SHVAT,
835        ADAR_I,
836        NISAN,
837        IYYAR,
838        SIVAN,
839        TAMUZ,
840        TISHREI,
841        TISHREI,
842        TISHREI,
843        CHESHVAN
844    }
845
846    local function greg2abs(year,month,day)
847        local y = year - 1
848        return y * 365 + div(y,4)- div(y,100) + div(y,400) + os.nofdays(year,month,day)
849    end
850
851    local function abs2greg(abs)
852
853        local d0   = abs - 1
854
855        local n400 = div(d0, 146097)
856        local d1   = mod(d0, 146097)
857        local n100 = div(d1,  36524)
858        local d2   = mod(d1,  36524)
859        local n4   = div(d2,   1461)
860        local d3   = mod(d2,   1461)
861        local n1   = div(d3,    365)
862
863        local day  = mod(d3, 365) + 1
864        local year = 400 * n400 + 100 * n100 + 4 * n4 + n1
865
866        if n100 == 4 or n1 == 4 then
867            return year, 12, 31
868        else
869            year = year + 1
870            month = 1
871            while true do
872                local mlen = os.nofdays(year,month)
873                if mlen < day then
874                    day   = day - mlen
875                    month = month + 1
876                else
877                    break
878                end
879            end
880            return year, month, day
881        end
882    end
883
884    local function hebrew_leapyear(year)
885        return mod(1 + year * 7, 19) < 7
886    end
887
888    local function hebrew_months_in_year(year)
889        return hebrew_leapyear(year) and 13 or 12
890    end
891
892    local function hebrew_elapsed_days(year)
893        local y         = year - 1
894        local m_elapsed = 235 * div(y,19) + 12 * mod(y,19) + div(((mod(y,19) * 7) + 1),19)
895        local p_elapsed = 204 + 793 * mod(m_elapsed,1080)
896        local h_elapsed = 5 + 12 * m_elapsed + 793 * div(m_elapsed,1080) + div(p_elapsed,1080)
897        local parts     = mod(p_elapsed,1080) + 1080 * mod(h_elapsed,24)
898        local day       = 1 + 29 * m_elapsed + div(h_elapsed,24)
899        local d         = mod(day,7)
900        local alt_day   = day
901        if       parts >= 19440
902             or (parts >=  9924 and d == 2 and not (hebrew_leapyear(year)))
903             or (parts >= 16789 and d == 1 and      hebrew_leapyear(y   )) then
904            alt_day = alt_day + 1
905        end
906        d = mod(alt_day,7)
907        if d == 0 or d == 3 or d == 5 then
908            alt_day = alt_day + 1
909        end
910        return alt_day
911    end
912
913    local function days_in_hebrew_year(year)
914        return hebrew_elapsed_days(year + 1) - hebrew_elapsed_days(year)
915    end
916
917    local function long_cheshvan(year)
918        return mod(days_in_hebrew_year(year),10) == 5
919    end
920
921    local function short_kislev(year)
922        return mod(days_in_hebrew_year(year),10) == 3
923    end
924
925    local function max_days_in_heb_month(month,year)
926        if month == IYYAR or month == TAMUZ or month == ELUL or month == TEVET or month == ADAR_II or
927           (month == ADAR_I   and not hebrew_leapyear(year)) or
928           (month == CHESHVAN and not long_cheshvan(year))   or
929           (month == KISLEV   and     short_kislev (year))   then
930            return 29
931        else
932            return 30
933        end
934    end
935
936    local function hebrew2abs(year,month,day)
937        if month < TISHREI then
938            for m=TISHREI, hebrew_months_in_year(year), 1 do
939                day = day + max_days_in_heb_month(m,year)
940            end
941            for m=NISAN, month - 1, 1 do
942                day = day + max_days_in_heb_month(m,year)
943            end
944        else
945            for m=TISHREI, month - 1, 1 do
946                day = day + max_days_in_heb_month(m,year)
947            end
948        end
949        return hebrew_elapsed_days(year) - 1373429 + day
950    end
951
952    local function abs2hebrew(abs)
953        local yy, mm, dd = abs2greg(abs)
954        local day   = 1
955        local month = TISHREI
956        local year  = 3760 + yy
957        while abs >= hebrew2abs(year+1,month,day) do
958            year = year + 1
959        end
960        if year >= 4635 and year < 10666 then
961            month = mmap[mm]
962        end
963        while abs > hebrew2abs(year,month,max_days_in_heb_month(month,year)) do
964            month = mod(month,hebrew_months_in_year(year)) + 1
965        end
966        day = abs - hebrew2abs(year,month,1) + 1
967        return year, month, day
968    end
969
970    -- months = { "ניסן" "אייר" "סיון" "תמוז" "אב" "אלול" "תשרי" "חשון" "כסלו" "טבת" "שבט" }
971
972    gregorian_to_hebrew = function(y,m,d)
973        return abs2hebrew(greg2abs(y,m,d))
974    end
975
976    converters.gregorian_to_hebrew = gregorian_to_hebrew
977
978end
979
980local gregorian_to_jalali, jalali_to_gregorian do
981
982    -- Well, since the one asking for this didn't test it the following code is not
983    -- enabled.
984    --
985    -- -- This Lua version is based on a Javascript by Behdad Esfahbod which in turn
986    -- -- is based on GPL'd code by Roozbeh Pournader of the The FarsiWeb Project
987    -- -- Group: http://www.farsiweb.info/jalali/jalali.js.
988    -- --
989    -- -- We start tables at one, I kept it zero based in order to stay close to
990    -- -- the original.
991    -- --
992    -- -- Conversion by Hans Hagen
993
994    local g_days_in_month = { [0] = 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
995    local j_days_in_month = { [0] = 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 }
996
997
998    gregorian_to_jalali = function(gy,gm,gd)
999        local jy, jm, jd, g_day_no, j_day_no, j_np, i
1000        gy, gm, gd = gy - 1600, gm - 1, gd - 1
1001        g_day_no = 365*gy + div((gy+3),4) - div((gy+99),100) + div((gy+399),400)
1002        i = 0
1003        while i < gm do
1004            g_day_no = g_day_no + g_days_in_month[i]
1005            i = i + 1
1006        end
1007        if (gm>1 and ((gy%4==0 and gy%100~=0) or (gy%400==0))) then
1008            g_day_no = g_day_no + 1
1009        end
1010        g_day_no = g_day_no + gd
1011        j_day_no = g_day_no - 79
1012        j_np = div(j_day_no,12053)
1013        j_day_no = mod(j_day_no,12053)
1014        jy = 979 + 33*j_np + 4*div(j_day_no,1461)
1015        j_day_no = mod(j_day_no,1461)
1016        if j_day_no >= 366 then
1017            jy = jy + div((j_day_no-1),365)
1018            j_day_no = mod((j_day_no-1),365)
1019        end
1020        i = 0
1021        while i < 11 and j_day_no >= j_days_in_month[i] do
1022            j_day_no = j_day_no - j_days_in_month[i]
1023            i = i + 1
1024        end
1025        jm = i + 1
1026        jd = j_day_no + 1
1027        return jy, jm, jd
1028    end
1029
1030    jalali_to_gregorian = function(jy,jm,jd)
1031        local gy, gm, gd, g_day_no, j_day_no, leap, i
1032        jy, jm, jd = jy - 979, jm - 1, jd - 1
1033        j_day_no = 365*jy + div(jy,33)*8 + div((mod(jy,33)+3),4)
1034        for i=0,jm-1,1 do
1035            j_day_no = j_day_no + j_days_in_month[i]
1036        end
1037        j_day_no = j_day_no + jd
1038        g_day_no = j_day_no + 79
1039        gy = 1600 + 400*div(g_day_no,146097)
1040        g_day_no = mod(g_day_no, 146097)
1041        leap = 1
1042        if g_day_no >= 36525 then
1043            g_day_no = g_day_no - 1
1044            gy = gy + 100*div(g_day_no,36524)
1045            g_day_no = mod(g_day_no, 36524)
1046            if g_day_no >= 365 then
1047                g_day_no = g_day_no + 1
1048            else
1049                leap = 0
1050            end
1051        end
1052        gy = gy  + 4*div(g_day_no,1461)
1053        g_day_no = mod(g_day_no, 1461)
1054        if g_day_no >= 366 then
1055            leap = 0
1056            g_day_no = g_day_no - 1
1057            gy = gy + div(g_day_no, 365)
1058            g_day_no = mod(g_day_no, 365)
1059        end
1060        i = 0
1061        while true do
1062            local d = g_days_in_month[i] + ((i == 1 and leap) or 0)
1063            if g_day_no >= d then
1064                g_day_no = g_day_no - d
1065                i = i + 1
1066            else
1067                break
1068            end
1069        end
1070        gm = i + 1
1071        gd = g_day_no + 1
1072        return gy, gm, gd
1073    end
1074
1075    -- local function test(yg,mg,dg,yj,mj,dj)
1076    --     local y1, m1, d1 = jalali_to_gregorian(yj,mj,dj)
1077    --     local y2, m2, d2 = gregorian_to_jalali(yg,mg,dg)
1078    --     print(y1 == yg and m1 == mg and d1 == dg, yg,mg,dg, y1,m1,d1)
1079    --     print(y2 == yj and m2 == mj and d2 == dj, yj,mj,dj, y2,m2,d2)
1080    -- end
1081
1082    -- test(1953,08,19, 1332,05,28)
1083    -- test(1979,02,11, 1357,11,22)
1084    -- test(2000,02,28, 1378,12,09)
1085    -- test(2000,03,01, 1378,12,11)
1086    -- test(2009,02,24, 1387,12,06)
1087    -- test(2015,03,21, 1394,01,01)
1088    -- test(2016,03,20, 1395,01,01)
1089
1090    converters.gregorian_to_jalali = gregorian_to_jalali
1091    converters.jalali_to_gregorian = jalali_to_gregorian
1092
1093end
1094
1095-- -- more efficient but needs testing
1096
1097-- local escapes = characters.filters.utf.private.escapes
1098
1099-- local function do_alphabetic(n,mapping,chr)
1100--     local max = #mapping
1101--     if n > max then
1102--         do_alphabetic(floor((n-1)/max),mapping,chr)
1103--         n = (n-1)%max+1
1104--     end
1105--     n = chr(n,mapping)
1106--     context(escapes[n] or utfchar(n))
1107-- end
1108
1109-- local lccodes, uccodes, safechar = characters.lccode, characters.uccode, commands.safechar
1110
1111-- local function do_alphabetic(n,mapping,chr)
1112--     local max = #mapping
1113--     if n > max then
1114--         do_alphabetic(floor((n-1)/max),mapping,chr)
1115--         n = (n-1)%max+1
1116--     end
1117--     safechar(chr(n,mapping))
1118-- end
1119
1120-- local function lowercased(n,mapping) return characters.lccode(mapping[n] or fallback) end
1121-- local function uppercased(n,mapping) return characters.uccode(mapping[n] or fallback) end
1122
1123-- function converters.alphabetic(n,code)
1124--     do_alphabetic(n,counters[code] or counters.default,lowercased) -- lccode catches wrong tables
1125-- end
1126
1127-- function converters.Alphabetic(n,code)
1128--     do_alphabetic(n,counters[code] or counters.default,uppercased)
1129-- end
1130
1131local ordinals = {
1132    english = function(n)
1133        local two = n % 100
1134        if two == 11 or two == 12 or two == 13 then
1135            return "th"
1136        else
1137            local one = n % 10
1138            if one == 1 then
1139                return "st"
1140            elseif one == 2 then
1141                return "nd"
1142            elseif one == 3 then
1143                return "rd"
1144            else
1145                return "th"
1146            end
1147        end
1148    end,
1149    dutch = function(n)
1150        return "e"
1151    end,
1152    french = function(n)
1153        if n == 1 then
1154            return "er"
1155        else
1156            return "e"
1157        end
1158    end,
1159}
1160
1161ordinals.en = ordinals.english
1162ordinals.nl = ordinals.dutch
1163ordinals.fr = ordinals.french
1164
1165function converters.ordinal(n,language)
1166    local t = language and ordinals[language]
1167    return t and t(n)
1168end
1169
1170local function ctxordinal(n,language)
1171    local t = language and ordinals[language]
1172    local o = t and t(n)
1173    context(n)
1174    if o then
1175        ctx_highordinalstr(o)
1176    end
1177end
1178
1179implement {
1180    name      = "ordinal",
1181    actions   = ctxordinal,
1182    arguments = { "integer", "string" }
1183}
1184
1185-- verbose numbers
1186
1187local data         = allocate()
1188local verbose      = { data = data }
1189converters.verbose = verbose
1190
1191-- verbose english
1192
1193local words = {
1194               [0] = "zero",
1195               [1] = "one",
1196               [2] = "two",
1197               [3] = "three",
1198               [4] = "four",
1199               [5] = "five",
1200               [6] = "six",
1201               [7] = "seven",
1202               [8] = "eight",
1203               [9] = "nine",
1204              [10] = "ten",
1205              [11] = "eleven",
1206              [12] = "twelve",
1207              [13] = "thirteen",
1208              [14] = "fourteen",
1209              [15] = "fifteen",
1210              [16] = "sixteen",
1211              [17] = "seventeen",
1212              [18] = "eighteen",
1213              [19] = "nineteen",
1214              [20] = "twenty",
1215              [30] = "thirty",
1216              [40] = "forty",
1217              [50] = "fifty",
1218              [60] = "sixty",
1219              [70] = "seventy",
1220              [80] = "eighty",
1221              [90] = "ninety",
1222             [100] = "hundred",
1223            [1000] = "thousand",
1224         [1000000] = "million",
1225      [1000000000] = "billion",
1226   [1000000000000] = "trillion",
1227}
1228
1229local function translate(n,connector)
1230    local w = words[n]
1231    if w then
1232        return w
1233    end
1234    local t = { }
1235    local function compose_one(n)
1236        local w = words[n]
1237        if w then
1238            t[#t+1] = w
1239            return
1240        end
1241        local a, b = floor(n/100), n % 100
1242        if a == 10 then
1243            t[#t+1] = words[1]
1244            t[#t+1] = words[1000]
1245        elseif a > 0 then
1246            t[#t+1] = words[a]
1247            t[#t+1] = words[100]
1248            -- don't say 'nine hundred zero'
1249            if b == 0 then
1250                return
1251            end
1252        end
1253        if words[b] then
1254            t[#t+1] = words[b]
1255        else
1256            a, b = floor(b/10), n % 10
1257            t[#t+1] = words[a*10]
1258            t[#t+1] = words[b]
1259        end
1260    end
1261    local function compose_two(n,m)
1262        if n > (m-1) then
1263            local a, b = floor(n/m), n % m
1264            if a > 0 then
1265                compose_one(a)
1266            end
1267            t[#t+1] = words[m]
1268            n = b
1269        end
1270        return n
1271    end
1272    n = compose_two(n,1000000000000)
1273    n = compose_two(n,1000000000)
1274    n = compose_two(n,1000000)
1275    n = compose_two(n,1000)
1276    if n > 0 then
1277        compose_one(n)
1278    end
1279    return #t > 0 and concat(t,connector or " ") or tostring(n)
1280end
1281
1282data.english = {
1283    words     = words,
1284    translate = translate,
1285}
1286
1287data.en = data.english
1288
1289-- print(translate(11111111))
1290-- print(translate(2221101))
1291-- print(translate(1111))
1292-- print(translate(1218))
1293-- print(translate(1234))
1294-- print(translate(12345))
1295-- print(translate(12345678900000))
1296
1297-- verbose spanish (unchecked)
1298
1299local words = {
1300               [1] = "uno",
1301               [2] = "dos",
1302               [3] = "tres",
1303               [4] = "cuatro",
1304               [5] = "cinco",
1305               [6] = "seis",
1306               [7] = "siete",
1307               [8] = "ocho",
1308               [9] = "nueve",
1309              [10] = "diez",
1310              [11] = "once",
1311              [12] = "doce",
1312              [13] = "trece",
1313              [14] = "catorce",
1314              [15] = "quince",
1315              [16] = "dieciséis",
1316              [17] = "diecisiete",
1317              [18] = "dieciocho",
1318              [19] = "diecinueve",
1319              [20] = "veinte",
1320              [21] = "veintiuno",
1321              [22] = "veintidós",
1322              [23] = "veintitrés",
1323              [24] = "veinticuatro",
1324              [25] = "veinticinco",
1325              [26] = "veintiséis",
1326              [27] = "veintisiete",
1327              [28] = "veintiocho",
1328              [29] = "veintinueve",
1329              [30] = "treinta",
1330              [40] = "cuarenta",
1331              [50] = "cincuenta",
1332              [60] = "sesenta",
1333              [70] = "setenta",
1334              [80] = "ochenta",
1335              [90] = "noventa",
1336             [100] = "ciento",
1337             [200] = "doscientos",
1338             [300] = "trescientos",
1339             [400] = "cuatrocientos",
1340             [500] = "quinientos",
1341             [600] = "seiscientos",
1342             [700] = "setecientos",
1343             [800] = "ochocientos",
1344             [900] = "novecientos",
1345            [1000] = "mil",
1346         [1000000] = "millón",
1347      [1000000000] = "mil millones",
1348   [1000000000000] = "billón",
1349}
1350
1351local function translate(n,connector)
1352    local w = words[n]
1353    if w then
1354        return w
1355    end
1356    local t = { }
1357    local function compose_one(n)
1358        local w = words[n]
1359        if w then
1360            t[#t+1] = w
1361            return
1362        end
1363        -- a, b = hundreds, remainder
1364        local a, b = floor(n/100), n % 100
1365        -- one thousand
1366        if a == 10 then
1367            t[#t+1] = words[1]
1368            t[#t+1] = words[1000]
1369        -- x hundred (n.b. this will not give thirteen hundred because
1370        -- compose_one(n) is only called after
1371        -- n = compose(two(n, 1000))
1372        elseif a > 0 then
1373            t[#t+1] = words[a*100]
1374        end
1375        -- the remainder
1376        if words[b] then
1377            t[#t+1] = words[b]
1378        else
1379            -- a, b = tens, remainder
1380            a, b = floor(b/10), n % 10
1381            t[#t+1] = words[a*10]
1382            t[#t+1] = "y"
1383            t[#t+1] = words[b]
1384        end
1385    end
1386    -- compose_two handles x billion, ... x thousand. When 1000 or less is
1387    -- left, compose_one takes over.
1388    local function compose_two(n,m)
1389        if n > (m-1) then
1390            local a, b = floor(n/m), n % m
1391            if a > 0 then
1392                compose_one(a)
1393            end
1394            t[#t+1] = words[m]
1395            n = b
1396        end
1397        return n
1398    end
1399    n = compose_two(n,1000000000000)
1400    n = compose_two(n,1000000000)
1401    n = compose_two(n,1000000)
1402    n = compose_two(n,1000)
1403    if n > 0 then
1404        compose_one(n)
1405    end
1406    return #t > 0 and concat(t,connector or " ") or tostring(n)
1407end
1408
1409data.spanish = {
1410    words     = words,
1411    translate = translate,
1412}
1413
1414data.es = data.spanish
1415
1416-- print(translate(31))
1417-- print(translate(101))
1418-- print(translate(199))
1419
1420-- verbose swedish by Peter Kvillegard
1421
1422do
1423
1424    local words = {
1425            [0] = "noll",
1426            [1] = "ett",
1427            [2] = "två",
1428            [3] = "tre",
1429            [4] = "fyra",
1430            [5] = "fem",
1431            [6] = "sex",
1432            [7] = "sju",
1433            [8] = "åtta",
1434            [9] = "nio",
1435           [10] = "tio",
1436           [11] = "elva",
1437           [12] = "tolv",
1438           [13] = "tretton",
1439           [14] = "fjorton",
1440           [15] = "femton",
1441           [16] = "sexton",
1442           [17] = "sjutton",
1443           [18] = "arton",
1444           [19] = "nitton",
1445           [20] = "tjugo",
1446           [30] = "trettio",
1447           [40] = "fyrtio",
1448           [50] = "femtio",
1449           [60] = "sextio",
1450           [70] = "sjuttio",
1451           [80] = "åttio",
1452           [90] = "nittio",
1453          [100] = "hundra",
1454         [10^3] = "tusen",
1455         [10^6] = "miljon",
1456         [10^9] = "miljard",
1457        [10^12] = "biljon",
1458        [10^15] = "biljard",
1459    }
1460
1461    local function translate(n,connector)
1462        local w = words[n]
1463        if w then
1464            return w
1465        else
1466            local t = { }
1467            local l = 0
1468            -- group of three digits to words, e.g. 123 -> etthundratjugotre
1469            local function triplets(n)
1470                if floor(n/100) > 0 then
1471                    l = l + 1 ; t[l] = words[floor(n/100)]
1472                    l = l + 1 ; t[l] = words[100]
1473                end
1474                if n%100 > 20 then
1475                    l = l + 1 ; t[l] = words[n%100-n%10]
1476                    if n%10 > 0 then
1477                        l = l + 1 ; t[l] = words[n%10]
1478                    end
1479                elseif n%100 > 0 then
1480                    l = l + 1 ; t[l] = words[n%100]
1481                end
1482            end
1483            -- loops through 10^15,10^12,...10^3, extracting groups of three digits
1484            -- to make words from, then adding names for order of magnitude
1485            for i=15,3,-3 do
1486                local triplet = floor(n/10^i)%10^3
1487                if triplet > 0 then
1488                    -- grammar: "en" instead of "ett"
1489                    if i > 3 and triplet == 1 then
1490                        l = l + 1 ; t[l] = "en"
1491                    else
1492                        triplets(triplet)
1493                    end
1494                    -- grammar: plural form of "millions" etc
1495                    l = l + 1 ; t[l] = words[10^i]
1496                    if i > 3 and triplet > 1 then
1497                        l = l + 1 ; t[l] = "er"
1498                    end
1499                end
1500            end
1501            -- add last group of three numbers (no word for magnitude)
1502            n = n%1000
1503            if n > 0 then
1504                triplets(n)
1505            end
1506            t = concat(t," ")
1507            -- grammar: spacing for numbers < 10^6 and repeated letters
1508            if n < 10^6 then
1509                t = gsub(t,"%stusen%s","tusen")
1510                t = gsub(t,"etttusen","ettusen")
1511            end
1512            return t
1513        end
1514    end
1515
1516    data.swedish = {
1517        words     = words,
1518        translate = translate,
1519    }
1520
1521    data.sv = data.swedish
1522
1523end
1524
1525-- verbose handler:
1526
1527function converters.verbose.translate(n,language,connector)
1528    local t = language and data[language]
1529    return t and t.translate(n,connector) or n
1530end
1531
1532local function verbose(n,language,connector)
1533    local t = language and data[language]
1534    context(t and t.translate(n,connector) or n)
1535end
1536
1537implement {
1538    name      = "verbose",
1539    actions   = verbose,
1540    arguments = { "integer", "string", "string" }
1541}
1542
1543-- These are just helpers but not really for the tex end. Do we have to
1544-- use translate here?
1545
1546local whitespace  = lpegpatterns.whitespace
1547local word        = lpegpatterns.utf8uppercharacter^-1 * (1-whitespace)^1
1548local pattern_one = Cs( whitespace^0 * word^-1 * P(1)^0)
1549local pattern_all = Cs((whitespace^1 + word)^1)
1550
1551function converters.word (s) return s end -- dummies for typos
1552function converters.words(s) return s end -- dummies for typos
1553
1554local function Word (s) return lpegmatch(pattern_one,s) or s end
1555local function Words(s) return lpegmatch(pattern_all,s) or s end
1556
1557converters.Word  = Word
1558converters.Words = Words
1559
1560converters.upper = characters.upper
1561converters.lower = characters.lower
1562
1563-- print(converters.Word("foo bar"))
1564-- print(converters.Word(" foo bar"))
1565-- print(converters.Word("123 foo bar"))
1566-- print(converters.Word(" 123 foo bar"))
1567
1568-- print(converters.Words("foo bar"))
1569-- print(converters.Words(" foo bar"))
1570-- print(converters.Words("123 foo bar"))
1571-- print(converters.Words(" 123 foo bar"))
1572
1573-- --
1574
1575local v_day      = variables.day
1576local v_year     = variables.year
1577local v_month    = variables.month
1578local v_weekday  = variables.weekday
1579local v_referral = variables.referral
1580local v_space    = variables.space
1581
1582local v_MONTH    = upper(v_month)
1583local v_WEEKDAY  = upper(v_weekday)
1584
1585local convert = converters.convert
1586
1587local days = { -- not variables
1588    "sunday",
1589    "monday",
1590    "tuesday",
1591    "wednesday",
1592    "thursday",
1593    "friday",
1594    "saturday",
1595}
1596
1597local months = { -- not variables
1598    "january",
1599    "february",
1600    "march",
1601    "april",
1602    "may",
1603    "june",
1604    "july",
1605    "august",
1606    "september",
1607    "october",
1608    "november",
1609    "december",
1610}
1611
1612local monthmnems = { -- not variables
1613    -- virtual table
1614}
1615
1616local daymnems = { -- not variables
1617    -- virtual table
1618}
1619
1620setmetatableindex(days,       function(t,k) return "unknown" end)
1621setmetatableindex(daymnems,   function(t,k) return days[k] .. ":mnem" end)
1622setmetatableindex(months,     function(t,k) return "unknown" end)
1623setmetatableindex(monthmnems, function(t,k) return months[k] .. ":mnem" end)
1624
1625do
1626
1627    local function dayname(n)
1628        ctx_labeltext(days[n])
1629    end
1630
1631    local function daymnem(n)
1632        ctx_labeltext(daymnems[n])
1633    end
1634
1635    local function weekdayname(day,month,year)
1636        ctx_labeltext(days[weekday(day,month,year)])
1637    end
1638
1639    local function monthname(n)
1640        ctx_labeltext(months[n])
1641    end
1642
1643    local function monthmnem(n)
1644        ctx_labeltext(monthmnems[n])
1645    end
1646
1647    implement {
1648        name      = "dayname",
1649        actions   = dayname,
1650        arguments = "integer",
1651    }
1652
1653    implement {
1654        name      = "daymnem",
1655        actions   = daymnem,
1656        arguments = "integer",
1657    }
1658
1659    implement {
1660        name      = "weekdayname",
1661        actions   = weekdayname,
1662        arguments = { "integer", "integer", "integer" }
1663    }
1664
1665    implement {
1666        name      = "monthname",
1667        actions   = monthname,
1668        arguments = "integer",
1669    }
1670
1671    implement {
1672        name      = "monthmnem",
1673        actions   = monthmnem,
1674        arguments = "integer",
1675    }
1676
1677    -- todo : short week days
1678
1679    local f_monthlong    = formatters["\\monthlong{%s}"]
1680    local f_monthshort   = formatters["\\monthshort{%s}"]
1681    local f_daylong      = formatters["\\daylong{%s}"]
1682    local f_dayshort     = formatters["\\dayshort{%s}"]
1683    local f_weekday      = formatters["\\weekday{%s}"]
1684    local f_dayoftheweek = formatters["\\dayoftheweek{%s}{%s}{%s}"]
1685
1686    local function tomonthlong (m) return f_monthlong (tonumber(m) or 1) end
1687    local function tomonthshort(m) return f_monthshort(tonumber(m) or 1) end
1688    local function todaylong   (d) return f_daylong   (tonumber(d) or 1) end
1689    local function todayshort  (d) return f_dayshort  (tonumber(d) or 1) end
1690    local function toweekday   (d) return f_weekday   (tonumber(d) or 1) end
1691
1692    local function todayoftheweek(d,m,y)
1693        return f_dayoftheweek(tonumber(d) or 1,tonumber(m) or 1,tonumber(y) or 2000)
1694    end
1695
1696    addformatter(formatters,"monthlong",   [[tomonthlong(%s)]],         { tomonthlong    = tomonthlong    })
1697    addformatter(formatters,"monthshort",  [[tomonthshort(%s)]],        { tomonthshort   = tomonthshort   })
1698    addformatter(formatters,"daylong",     [[todaylong(%s)]],           { todaylong      = todaylong      })
1699    addformatter(formatters,"dayshort",    [[todayshort(%s)]],          { todayshort     = todayshort     })
1700    addformatter(formatters,"weekday",     [[toweekday(%s)]],           { toweekday      = toweekday      })
1701    addformatter(formatters,"dayoftheweek",[[todayoftheweek(%s,%s,%s)]],{ todayoftheweek = todayoftheweek })
1702
1703    -- using %t is slower, even with caching as we seldom use > 3 items per epoch
1704
1705    local function toeyear  (e) return osdate("%Y",tonumber(e))  end
1706    local function toemonth (e) return osdate("%m",tonumber(e))  end
1707    local function toeday   (e) return osdate("%d",tonumber(e))  end
1708    local function toeminute(e) return osdate("%M",tonumber(e))  end
1709    local function toesecond(e) return osdate("%S",tonumber(e))  end
1710
1711    local function toemonthlong(e)
1712        return f_monthlong(tonumber(osdate("%m",tonumber(e))))
1713    end
1714
1715    local function toemonthshort(e)
1716        return f_monthshort(tonumber(osdate("%m",tonumber(e))))
1717    end
1718
1719    local function toedaylong(e)
1720        return f_datlong(tonumber(osdate("%w",tonumber(e))))
1721    end
1722
1723    local function toedayshort(e)
1724        return f_dayshort(tonumber(osdate("%w",tonumber(e))))
1725    end
1726
1727    local function toeweek(e) -- we run from 1-7 not 0-6
1728        return tostring(tonumber(osdate("%w",tonumber(e)))+1)
1729    end
1730
1731    local function toeweekday(e)
1732        return f_weekday(tonumber(osdate("%w",tonumber(e)))+1)
1733    end
1734
1735    local function toedate(format,e)
1736        return osdate(format,tonumber(e))
1737    end
1738
1739    addformatter(formatters,"eyear",        [[toeyear(%s)]],        { toeyear         = toeyear       })
1740    addformatter(formatters,"emonth",       [[toemonth(%s)]],       { toemonth        = toemonth      })
1741    addformatter(formatters,"eday",         [[toeday(%s)]],         { toeday          = toeday        })
1742    addformatter(formatters,"eweek",        [[toeweek(%s)]],        { toeweek         = toeweek       })
1743    addformatter(formatters,"eminute",      [[toeminute(%s)]],      { toeminute       = toeminute     })
1744    addformatter(formatters,"esecond",      [[toesecond(%s)]],      { toesecond       = toesecond     })
1745
1746    addformatter(formatters,"emonthlong",   [[toemonthlong(%s)]],   { toemonthlong    = toemonthlong  })
1747    addformatter(formatters,"emonthshort",  [[toemonthshort(%s)]],  { toemonthshort   = toemonthshort })
1748    addformatter(formatters,"edaylong",     [[toedaylong(%s)]],     { toedaylong      = toedaylong    })
1749    addformatter(formatters,"edayshort",    [[toedayshort(%s)]],    { toedayshort     = toedayshort   })
1750    addformatter(formatters,"eweekday",     [[toeweekday(%s)]],     { toeweekday      = toeweekday    })
1751
1752    addformatter(formatters,"edate",        [[toedate(%s,%s)]],     { toedate         = toedate       })
1753
1754end
1755
1756-- a prelude to a function that we can use at the lua end
1757
1758-- day:ord month:mmem
1759-- j and jj obsolete
1760
1761local spaced = {
1762    [v_year]    = true,
1763    [v_month]   = true,
1764    [v_MONTH]   = true,
1765    [v_day]     = true,
1766    [v_weekday] = true,
1767    [v_WEEKDAY] = true,
1768}
1769
1770local dateconverters = {
1771    ["hebrew:to"]   = gregorian_to_hebrew,
1772    ["jalali:to"]   = gregorian_to_jalali,
1773    ["jalali:from"] = jalali_to_gregorian,
1774}
1775
1776local variants = {
1777    mnem   = {
1778        month = monthmnems,
1779        day   = daymnems,
1780    },
1781    hebrew = {
1782        month = setmetatableindex(function(t,k) return months[k] .. ":hebrew" end),
1783        day   = setmetatableindex(function(t,k) return days  [k] .. ":hebrew" end),
1784    },
1785    jalali = {
1786        month = setmetatableindex(function(t,k) return months[k] .. ":jalali" end),
1787        day   = setmetatableindex(function(t,k) return days  [k] .. ":jalali" end),
1788    },
1789}
1790
1791do
1792
1793    local function currentdate(str,currentlanguage,year,month,day) -- second argument false : no label
1794        local list       = utilities.parsers.settings_to_array(str)
1795        local splitlabel = languages.labels.split or string.itself -- we need to get the loading order right
1796     -- local year       = tex.year
1797     -- local month      = tex.month
1798     -- local day        = tex.day
1799        local auto       = true
1800        if currentlanguage == "" then
1801            currentlanguage = false
1802        end
1803        for i=1,#list do
1804            local entry = list[i]
1805            local convert = dateconverters[entry]
1806            if convert then
1807                year, month, day = convert(year,month,day)
1808            else
1809                local tag, plus = splitlabel(entry)
1810                local ordinal, mnemonic, whatordinal, highordinal = false, false, nil, false
1811                if not tag then
1812                    tag = entry
1813                elseif plus == "+" or plus == "ord" then
1814                    ordinal = true
1815                elseif plus == "++" or plus == "highord" then
1816                    ordinal = true
1817                    highordinal = true
1818                elseif plus then -- mnem MNEM etc
1819                    mnemonic = variants[plus]
1820                end
1821                if not auto and spaced[tag] then
1822                    ctx_space()
1823                end
1824                auto = false
1825                if tag == v_year or tag == "y" or tag == "Y" then
1826                    if plus then
1827                        plus = converters[plus]
1828                    end
1829                    if plus then
1830                        context(plus(year))
1831                    elseif currentlanguage == false then
1832                        context(year)
1833                    else
1834                        ctx_convertnumber(v_year,year)
1835                    end
1836                elseif tag == "yy" or tag == "YY" then
1837                    context("%02i",year % 100)
1838                elseif tag == v_month or tag == "m" then
1839                    if currentlanguage == false then
1840                        context(Word(months[month]))
1841                    else
1842                        if type(mnemonic) == "table" then
1843                            mnemonic = mnemonic.month
1844                        end
1845                        if mnemonic then
1846                            ctx_labeltext(variables[mnemonic[month]])
1847                        else
1848                            ctx_labeltext(variables[months[month]])
1849                        end
1850                    end
1851                elseif tag == v_MONTH then
1852                    if currentlanguage == false then
1853                        context(Word(variables[months[month]]))
1854                    else
1855                        if type(mnemonic) == "table" then
1856                            mnemonic = mnemonic.month
1857                        end
1858                        if mnemonic then
1859                            ctx_LABELTEXT(variables[mnemonic[month]])
1860                        else
1861                            ctx_LABELTEXT(variables[months[month]])
1862                        end
1863                    end
1864                elseif tag == "mm" then
1865                    context("%02i",month)
1866                elseif tag == "M" then
1867                    context(month)
1868                elseif tag == v_day or tag == "d" then
1869                    if plus then
1870                        plus = converters[plus]
1871                    end
1872                    if plus then
1873                        context(plus(day))
1874                    elseif currentlanguage == false then
1875                        context(day)
1876                    else
1877                        ctx_convertnumber(v_day,day)
1878                    end
1879                    whatordinal = day
1880                elseif tag == "dd" then
1881                    context("%02i",day)
1882                    whatordinal = day
1883                elseif tag == "D" then
1884                    context(day)
1885                    whatordinal = day
1886                elseif tag == v_weekday or tag == "w" then
1887                    local wd = weekday(day,month,year)
1888                    if currentlanguage == false then
1889                        context(Word(days[wd]))
1890                    else
1891                        if type(mnemonic) == "table" then
1892                            mnemonic = mnemonic.day
1893                        end
1894                        if mnemonic then
1895                            ctx_labeltext(variables[mnemonic[wd]])
1896                        else
1897                            ctx_labeltext(variables[days[wd]])
1898                        end
1899                    end
1900                elseif tag == v_WEEKDAY then
1901                    local wd = weekday(day,month,year)
1902                    if currentlanguage == false then
1903                        context(Word(days[wd]))
1904                    else
1905                        if type(mnemonic) == "table" then
1906                            mnemonic = mnemonic.day
1907                        end
1908                        if mnemonic then
1909                            ctx_LABELTEXT(variables[mnemonic[wd]])
1910                        else
1911                            ctx_LABELTEXT(variables[days[wd]])
1912                        end
1913                    end
1914                elseif tag == "W" then
1915                    context(weekday(day,month,year))
1916                elseif tag == v_referral then
1917                    context("%04i%02i%02i",year,month,day)
1918                elseif tag == v_space or tag == "\\ " then
1919                    ctx_space()
1920                    auto = true
1921                elseif tag ~= "" then
1922                    context(tag)
1923                    auto = true
1924                end
1925                if ordinal and whatordinal then
1926                    if currentlanguage == false then
1927                        -- ignore
1928                    else
1929                        context[highordinal and "highordinalstr" or "ordinalstr"](converters.ordinal(whatordinal,currentlanguage))
1930                    end
1931                end
1932            end
1933        end
1934    end
1935
1936    implement {
1937        name      = "currentdate",
1938        arguments = { "string", "string", "string", "integer", "integer", "integer" },
1939        actions   = function(pattern,default,language,year,month,day)
1940            currentdate(
1941                pattern  == "" and default or pattern,
1942                language == "" and false   or language,
1943                year, month, day
1944            )
1945        end,
1946    }
1947
1948    local function todate(s,y,m,d)
1949        if y or m or d then
1950            return formatters["\\date[y=%s,m=%s,d=%s][%s]\\relax"](y or "",m or "",d or "",s or "")
1951        else
1952            return formatters["\\currentdate[%s]\\relax"](s)
1953        end
1954    end
1955
1956    addformatter(formatters,"date", [[todate(...)]], { todate = todate })
1957
1958    -- context("one: %4!date!","MONTH",2020,12,11)          context.par()
1959    -- context("one: %4!date!","month",2020,12,11)          context.par()
1960    -- context("one: %4!date!","year,-,mm,-,dd",2020,12,11) context.par()
1961
1962    -- context("two: %3!date!","MONTH",false,12)          context.par()
1963    -- context("two: %3!date!","month",false,12)          context.par()
1964    -- context("two: %3!date!","year,-,mm,-,dd",false,12) context.par()
1965
1966end
1967
1968implement {
1969    name      = "unihex",
1970    arguments = "integer",
1971    actions   = { formatters["U+%05X"], context },
1972}
1973
1974-- totime might move to utilities.parsers as more general helper
1975
1976local n = R("09")^1 / tonumber -- lpegpatterns.digit
1977
1978local p = Cf( Ct("")
1979    -- year is mandate, month and day are optional
1980    * Cg(Cc("year") * n)
1981    * S("-/")^-1
1982    * Cg(Cc("month") * (n + Cc(1)))
1983    * S("-/")^-1
1984    * Cg(Cc("day") * (n + Cc(1)))
1985    -- time is optional, hour and minuta are mandate, seconds are optional
1986    * (
1987          whitespace^0
1988        * P("T")^-1
1989        * whitespace^0
1990        * Cg(Cc("hour") * n)
1991        * P(":")^-1
1992        * Cg(Cc("min") * n)
1993        * P(":")^-1
1994        * Cg(Cc("sec") * (n + Cc(0)))
1995    )^-1
1996    -- zone is optional, hour is mandate, minutes are optional
1997    * (
1998          whitespace^0
1999        * Cg(Cc("tzs") * (P("+") * Cc(1) + P("-") * Cc(-1) + Cc(1)))
2000        * whitespace^0
2001        * Cg(Cc("tzh") * n)
2002        * P(":")^-1
2003        * Cg(Cc("tzm") * (n + Cc(0)))
2004    )^-1
2005    , rawset)
2006
2007function converters.totime(s)
2008    if not s then
2009        return
2010    elseif type(s) == "table" then
2011        return s
2012    elseif type(s) == "string" then
2013        local t = lpegmatch(p,s)
2014        if not t then
2015            logs.report("system","invalid time specification %a",s)
2016        elseif t.tzh then
2017            local localtzh, localtzm = ostimezone(true)
2018            t.hour = t.hour + localtzh - t.tzs * t.tzh
2019            t.min  = t.min  + localtzm - t.tzs * t.tzm
2020        end
2021        return t
2022    end
2023    local n = tonumber(s)
2024    if n and n >= 0 then
2025        return osdate("*t",n)
2026    end
2027end
2028
2029function converters.settime(t)
2030    if type(t) ~= "table" then
2031        t = converters.totime(t)
2032    end
2033    if t then
2034        texset("year", t.year or 1000)
2035        texset("month", t.month or 1)
2036        texset("day", t.day or 1)
2037        texset("time", (t.hour or 0) * 60 + (t.min or 0))
2038    end
2039end
2040
2041-- taken from x-asciimath (where we needed it for a project)
2042
2043local d_one         = lpegpatterns.digit
2044local d_two         = d_one * d_one
2045local d_three       = d_two * d_one
2046local d_four        = d_three * d_one
2047local d_split       = P(-1) + Carg(2) * (lpegpatterns.period /"")
2048
2049local d_spaced      = (Carg(1) * d_three)^1
2050
2051local digitized_1   = Cs ( (
2052                        d_three * d_spaced * d_split +
2053                        d_two   * d_spaced * d_split +
2054                        d_one   * d_spaced * d_split +
2055                        P(1)
2056                      )^1 )
2057
2058local p_fourbefore  = d_four * d_split
2059local p_fourafter   = d_four * P(-1)
2060
2061local p_beforesplit = d_three * d_spaced^0 * d_split
2062                    + d_two   * d_spaced^0 * d_split
2063                    + d_one   * d_spaced^0 * d_split
2064                    + d_one   * d_split
2065
2066local p_aftersplit  = p_fourafter
2067                    + d_three * d_spaced
2068                    + d_two   * d_spaced
2069                    + d_one   * d_spaced
2070
2071local digitized_2   = Cs (
2072                         p_fourbefore  *  (p_aftersplit^0) +
2073                         p_beforesplit * ((p_aftersplit + d_one^1)^0)
2074                      )
2075
2076local p_fourbefore  = d_four * d_split
2077local p_fourafter   = d_four
2078local d_spaced      = (Carg(1) * (d_three + d_two + d_one))^1
2079local p_aftersplit  = p_fourafter * P(-1)
2080                    + d_three * d_spaced * P(1)^0
2081                    + d_one^1
2082
2083local digitized_3   = Cs((p_fourbefore + p_beforesplit) * p_aftersplit^0)
2084
2085local digits_space  = utfchar(0x2008)
2086
2087local splitmethods  = {
2088    digitized_1,
2089    digitized_2,
2090    digitized_3,
2091}
2092
2093local replacers     = table.setmetatableindex(function(t,k)
2094    local v = lpeg.replacer(".",k)
2095    t[k] = v
2096    return v
2097end)
2098
2099function converters.spaceddigits(settings,data)
2100    local data = tostring(data or settings.data or "")
2101    if data ~= "" then
2102        local method = settings.method
2103        local split  = splitmethods[tonumber(method) or 1]
2104        if split then
2105            local symbol    = settings.symbol
2106            local separator = settings.separator
2107            if not symbol or symbol == "" then
2108                symbol = "."
2109            end
2110            if type(separator) ~= "string" or separator == "" then
2111                separator = digits_space
2112            end
2113            local result = lpegmatch(split,data,1,separator,symbol)
2114            if not result and symbol ~= "." then
2115                result = lpegmatch(replacers[symbol],data)
2116            end
2117            if result then
2118             -- print(method,symbol,separator,data,result)
2119                return result
2120            end
2121        end
2122    end
2123    return str
2124end
2125
2126-- method 2 : split 3 before and 3 after
2127-- method 3 : split 3 before and 3 after with > 4 before
2128
2129-- symbols is extra split (in addition to period)
2130
2131-- local setup = { splitmethod = 3, symbol = "," }
2132-- local setup = { splitmethod = 2, symbol = "," }
2133-- local setup = { splitmethod = 1, symbol = "," }
2134--
2135-- local t = {
2136--     "0.00002",
2137--     "1", "12", "123", "1234", "12345", "123456", "1234567", "12345678", "123456789",
2138--     "1.1",
2139--     "12.12",
2140--     "123.123",
2141--     "1234.123",
2142--     "1234.1234",
2143--     "12345.1234",
2144--     "1234.12345",
2145--     "12345.12345",
2146--     "123456.123456",
2147--     "1234567.1234567",
2148--     "12345678.12345678",
2149--     "123456789.123456789",
2150--     "0.1234",
2151--     "1234.0",
2152--     "1234.00",
2153--     "0.123456789",
2154--     "100.00005",
2155--     "0.80018",
2156--     "10.80018",
2157--     "100.80018",
2158--     "1000.80018",
2159--     "10000.80018",
2160-- }
2161--
2162-- for i=1,#t do
2163--     print(formatters["%-20s : [%s]"](t[i],converters.spaceddigits(setup,t[i])))
2164-- end
2165
2166implement {
2167    name      = "spaceddigits",
2168    actions   = { converters.spaceddigits, context },
2169    arguments = {
2170        {
2171            { "symbol" },
2172            { "separator" },
2173            { "data" },
2174            { "method" },
2175        }
2176    }
2177}
2178
2179local function field(n) return context(osdate("*t")[n]) end
2180
2181implement { name = "actualday",   public = true, actions = function() field("day")   end }
2182implement { name = "actualmonth", public = true, actions = function() field("month") end }
2183implement { name = "actualyear",  public = true, actions = function() field("year")  end }
2184
2185implement {
2186    name    = "uuid",
2187    public  = true,
2188    actions = { os.uuid, context },
2189}
2190