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