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