phys-dim.lua /size: 38 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['phys-dim'] = {
2    version   = 1.001,
3    comment   = "companion to phys-dim.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-- This is pretty old code that I found back, but let's give it a try
10-- in practice. It started out as m-units.lua but as we want to keep that
11-- module around we moved the code to the dimensions module.
12--
13-- todo: maybe also an sciunit command that converts to si units (1 inch -> 0.0254 m)
14-- etc .. typical something to do when listening to a news whow or b-movie
15--
16-- todo: collect used units for logging (and list of units, but then we need
17-- associations too).
18
19-- The lists have been checked and completed by Robin Kirkham.
20
21-- dubious/wrong
22
23--  Atom                        = [[u]], -- should be amu (atomic mass unit)
24--  Bell                        = [[B]], -- should be bel
25--  Sterant                     = [[sr]], -- should be steradian
26--  Equivalent                  = [[eql]], -- qualifier?
27--  At                          = [[at]], -- qualifier?
28--  Force                       = [[f]], -- qualifier?
29--  eVolt                       = [[eV]],
30--  -- AC or DC voltages should be qualified in the text
31--  VoltAC                      = [[V\unitsbackspace\unitslower{ac}]],
32--  VoltDC                      = [[V\unitsbackspace\unitslower{dc}]],
33--  AC                          = [[V\unitsbackspace\unitslower{ac}]],
34--  DC                          = [[V\unitsbackspace\unitslower{dc}]],
35--  -- probably not harmful but there are better alternatives
36--  -- e.g., revolution per second (rev/s)
37--  RPS                         = [[RPS]],
38--  RPM                         = [[RPM]],
39--  RevPerSec                   = [[RPS]],
40--  RevPerMin                   = [[RPM]],
41
42local rawset, next = rawset, next
43local V, P, S, R, C, Cc, Cs, matchlpeg = lpeg.V, lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Cc, lpeg.Cs, lpeg.match
44local format, lower, gsub = string.format, string.lower, string.gsub
45local appendlpeg = lpeg.append
46local utfchartabletopattern = lpeg.utfchartabletopattern
47local mergetable, mergedtable, keys, loweredkeys = table.merge, table.merged, table.keys, table.loweredkeys
48local setmetatablenewindex = table.setmetatablenewindex
49local utfchar = utf.char
50
51physics            = physics or { }
52physics.units      = physics.units or { }
53
54local allocate     = utilities.storage.allocate
55
56local context      = context
57local commands     = commands
58local implement    = interfaces.implement
59
60local trace_units  = false
61local report_units = logs.reporter("units")
62
63trackers.register("physics.units", function(v) trace_units = v end)
64
65-- digits parser (todo : use patterns)
66
67local math_one       = Cs((P("$")    /"") * (1-P("$"))^1 * (P("$")/"")) / context.m
68local math_two       = Cs((P("\\m {")/"") * (1-P("}"))^1 * (P("}")/"")) / context.m -- watch the space after \m
69
70local digit          = R("09")
71local plus           = P("+")
72local minus          = P("-")
73local plusminus      = P("±")
74local sign           = plus + minus
75local power          = S("^e")
76local digitspace     = S("~@_")
77local comma          = P(",")
78local period         = P(".")
79local semicolon      = P(";")
80local colon          = P(":")
81local signspace      = P("/")
82local positive       = P("++") -- was p
83local negative       = P("--") -- was n
84local highspace      = P("//") -- was s
85local padding        = P("=")
86local space          = P(" ")
87local lparent        = P("(")
88local rparent        = P(")")
89
90local lbrace         = P("{")
91local rbrace         = P("}")
92
93local digits         = digit^1
94
95local powerdigits    = plus  * C(digits) / context.digitspowerplus
96                     + minus * C(digits) / context.digitspowerminus
97                     +         C(digits) / context.digitspower
98
99local ddigitspace    = digitspace  / "" / context.digitsspace
100local ddigit         = digits           / context.digitsdigit
101local dsemicomma     = semicolon   / "" / context.digitsseparatorspace
102local dsemiperiod    = colon       / "" / context.digitsseparatorspace
103local dfinalcomma    = comma       / "" / context.digitsfinalcomma
104local dfinalperiod   = period      / "" / context.digitsfinalperiod
105local dintercomma    = comma       / "" / context.digitsintermediatecomma
106local dinterperiod   = period      / "" / context.digitsintermediateperiod
107local dskipcomma     = comma       / "" / context.digitsseparatorspace
108local dskipperiod    = period      / "" / context.digitsseparatorspace
109local dsignspace     = signspace   / "" / context.digitssignspace
110local dpositive      = positive    / "" / context.digitspositive
111local dnegative      = negative    / "" / context.digitsnegative
112local dhighspace     = highspace   / "" / context.digitshighspace
113local dsomesign      = plus        / "" / context.digitsplus
114                     + minus       / "" / context.digitsminus
115                     + plusminus   / "" / context.digitsplusminus
116local dpower         = power       / "" * ( powerdigits + lbrace * powerdigits * rbrace )
117
118local dpadding       = padding     / "" / context.digitszeropadding -- todo
119
120local dleader        = (dpositive + dnegative + dhighspace + dsomesign + dsignspace)^0
121local dtrailer       = dpower^0
122local dfinal         = P(-1) + #P(1 - comma - period - semicolon - colon)
123local dnumber        = (ddigitspace + ddigit)^1
124
125-- ___,000,000  ___,___,000  ___,___,__0  000,000,000  000.00  000,000,000.00  000,000,000.==
126
127-- : ; for the moment not used, maybe for invisible fraction . , when no leading number
128
129-- local c_p = (ddigitspace^1 * dskipcomma)^0            -- ___,
130--           * (ddigitspace^0 * ddigit * dintercomma)^0  -- _00, 000,
131--           * ddigitspace^0  * ddigit^0                 -- _00 000
132--           * (
133--              dfinalperiod * ddigit                    -- .00
134--            + dskipperiod  * dpadding^1                -- .==
135--            + dsemiperiod  * ddigit                    -- :00
136--            + dsemiperiod  * dpadding^1                -- :==
137--             )^0
138--           + ddigit                                    -- 00
139--
140-- local p_c = (ddigitspace^1 * dskipperiod)^0           -- ___.
141--           * (ddigitspace^0 * ddigit * dinterperiod)^0 -- _00. 000.
142--           * ddigitspace^0  * ddigit^0                 -- _00 000
143--           * (
144--              dfinalcomma * ddigit                     -- ,00
145--            + dskipcomma  * dpadding^1                 -- ,==
146--            + dsemicomma  * ddigit                     -- :00
147--            + dsemicomma  * dpadding^1                 -- :==
148--             )^0
149--           + ddigit                                    -- 00
150--
151-- fix by WS/SB (needs further testing)
152
153local c_p = (ddigitspace^1 * dskipcomma)^0                    -- ___,
154          * (ddigitspace^0 * ddigit * dintercomma)^0          -- _00, 000,
155          * ddigitspace^0  * ddigit^0                         -- _00 000
156          * (
157             dfinalperiod * ddigit^1 * dpadding^1             -- .0=
158           + dfinalperiod * ddigit * (dintercomma * ddigit)^0 -- .00
159           + dskipperiod  * dpadding^1                        -- .==
160           + dsemiperiod  * ddigit * (dintercomma * ddigit)^0 -- :00
161           + dsemiperiod  * dpadding^1                        -- :==
162            )^0
163          + ddigit                                            -- 00
164
165local p_c = (ddigitspace^1 * dskipperiod)^0                   -- ___.
166          * (ddigitspace^0 * ddigit * dinterperiod)^0         -- _00. 000.
167          * ddigitspace^0  * ddigit^0                         -- _00 000
168          * (
169             dfinalcomma * ddigit^1 * dpadding^1              -- ,0=
170           + dfinalcomma * ddigit * (dinterperiod * ddigit)^0 -- 00
171           + dskipcomma  * dpadding^1                         -- ,==
172           + dsemicomma  * ddigit * (dinterperiod * ddigit)^0 -- :00
173           + dsemicomma  * dpadding^1                         -- :==
174            )^0
175          + ddigit                                            -- 00
176
177local p_c_dparser = math_one + math_two + dleader * p_c * dtrailer * dfinal
178local c_p_dparser = math_one + math_two + dleader * c_p * dtrailer * dfinal
179
180local function makedigits(str,reverse)
181    if reverse then
182        matchlpeg(p_c_dparser,str)
183    else
184        matchlpeg(c_p_dparser,str)
185    end
186end
187
188-- tables:
189
190local long_prefixes = {
191
192    -- Le Système international d'unités (SI) 8e édition (Table 5)
193
194    Yocto = "yocto",  -- 10^{-24}
195    Zepto = "zepto",  -- 10^{-21}
196    Atto  = "atto",   -- 10^{-18}
197    Femto = "femto",  -- 10^{-15}
198    Pico  = "pico",   -- 10^{-12}
199    Nano  = "nano",   -- 10^{-9}
200    Micro = "micro",  -- 10^{-6}
201    Milli = "milli",  -- 10^{-3}
202    Centi = "centi",  -- 10^{-2}
203    Deci  = "deci",   -- 10^{-1}
204
205    Deca  = "deca",   -- 10^{1}
206    Hecto = "hecto",  -- 10^{2}
207    Kilo  = "kilo",   -- 10^{3}
208    Mega  = "mega",   -- 10^{6}
209    Giga  = "giga",   -- 10^{9}
210    Tera  = "tera",   -- 10^{12}
211    Peta  = "peta",   -- 10^{15}
212    Exa   = "exa",    -- 10^{18}
213    Zetta = "zetta",  -- 10^{21}
214    Yotta = "yotta",  -- 10^{24}
215
216    -- IEC 60027-2: 2005, third edition, Part 2
217
218    Kibi  = "kibi", -- 2^{10} (not ki)
219    Mebi  = "mebi", -- 2^{20}
220    Gibi  = "gibi", -- 2^{30}
221    Tebi  = "tebi", -- 2^{40}
222    Pebi  = "pebi", -- 2^{50}
223    Exbi  = "exbi", -- 2^{60}
224
225    -- not standard
226
227    Zebi  = "zebi", -- binary
228    Yobi  = "yobi", -- binary
229
230    Micro = "micro",
231    Root  = "root",
232}
233
234local long_units = {
235
236    -- Le Système international d'unités (SI) 8e édition (except synonyms)
237    -- SI base units (Table 1)
238
239    Meter                       = "meter",
240    Gram                        = "gram",
241    Second                      = "second",
242    Ampere                      = "ampere",
243    Kelvin                      = "kelvin",
244    Mole                        = "mole",
245    Candela                     = "candela",
246
247    -- synonyms
248
249    Mol                         = "mole",
250    Metre                       = "meter",
251
252    -- SI derived units with special names (Table 3)
253
254    Radian                      = "radian",
255    Steradian                   = "steradian",
256    Hertz                       = "hertz",
257    Newton                      = "newton",
258    Pascal                      = "pascal",
259    Joule                       = "joule",
260    Watt                        = "watt",
261    Coulomb                     = "coulomb",
262    Volt                        = "volt",
263    Farad                       = "farad",
264    Ohm                         = "ohm",
265    Siemens                     = "siemens",
266    Weber                       = "weber",
267    Tesla                       = "tesla",
268    Henry                       = "henry",
269    Celsius                     = "celsius",
270    Lumen                       = "lumen",
271    Lux                         = "lux",
272    Becquerel                   = "becquerel",
273    Gray                        = "gray",
274    Sievert                     = "sievert",
275    Katal                       = "katal",
276
277    -- non SI units accepted for use with SI (Table 6)
278
279    Minute                      = "minute",
280    Hour                        = "hour",
281    Day                         = "day",
282
283    -- (degree, minute, second of arc are treated specially later)
284
285    Gon                         = "gon",
286    Grad                        = "grad",
287    Hectare                     = "hectare",
288    Liter                       = "liter",
289
290    Tonne                       = "tonne",
291
292    -- synonyms
293
294    MetricTon                   = "tonne",
295    Litre                       = "liter",
296
297    ["Metric Ton"]              = "tonne",
298
299    -- non-SI units whose values must be obtained experimentally (Table 7)
300
301    AtomicMassUnit              = "atomicmassunit",
302    AstronomicalUnit            = "astronomicalunit",
303    ElectronVolt                = "electronvolt",
304    Dalton                      = "dalton",
305
306    ["Atomic Mass Unit"]        = "atomicmassunit",
307    ["Astronomical Unit"]       = "astronomicalunit",
308    ["Electron Volt"]           = "electronvolt",
309
310    -- special cases (catch doubles, okay, a bit over the top)
311
312    DegreesCelsius              = "celsius",
313    DegreesFahrenheit           = "fahrenheit",
314    DegreeCelsius               = "celsius",
315    DegreeFahrenheit            = "fahrenheit",
316
317    ["Degrees Celsius"]         = "celsius",
318    ["Degrees Fahrenheit"]      = "fahrenheit",
319    ["Degree Celsius"]          = "celsius",
320    ["Degree Fahrenheit"]       = "fahrenheit",
321
322 -- too late as we already have connected symbols catched:
323 --
324 -- ["° Celsius"]               = "celsius",
325 -- ["° Fahrenheit"]            = "fahrenheit",
326 -- ["°Celsius"]                = "celsius",
327 -- ["°Fahrenheit"]             = "fahrenheit",
328
329    -- the "natural units" and "atomic units" are omitted for now
330    -- synonyms
331
332    eV                          = "electronvolt",
333    AMU                         = "atomicmassunit",
334
335    -- other non-SI units (Table 8)
336
337    Bar                         = "bar",
338    Hg                          = "mercury",
339 -- ["Millimetre Of Mercury"]   = [[mmHg]],
340    Angstrom                    = "angstrom", -- strictly Ångström
341    NauticalMile                = "nauticalmile",
342    Barn                        = "barn",
343    Knot                        = "knot",
344    Neper                       = "neper",
345    Bel                         = "bel", -- in practice only decibel used
346
347    ["Nautical Mile"]           = "nauticalmile",
348
349    -- other non-SI units from CGS system (Table 9)
350
351    Erg                         = "erg",
352    Dyne                        = "dyne",
353    Poise                       = "poise",
354    Stokes                      = "stokes",
355    Stilb                       = "stilb",
356    Phot                        = "phot",
357    Gal                         = "gal",
358    Maxwell                     = "maxwell",
359    Gauss                       = "gauss",
360    Oersted                     = "oersted",
361
362    -- end of SI
363
364    -- data: for use with the binary prefixes (except Erlang)
365
366    Bit                         = "bit",
367    Byte                        = "byte" ,
368    Baud                        = "baud",
369    Erlang                      = "erlang",
370
371    -- common units, not part of SI
372
373    Atmosphere                  = "atmosphere",
374    Revolution                  = "revolution",
375
376    -- synonyms
377
378    Atm                         = "atmosphere",
379    Rev                         = "revolution",
380
381    -- imperial units (very incomplete)
382
383    Fahrenheit                  = "fahrenheit",
384    Foot                        = "foot",
385    Inch                        = "inch",
386    Calorie                     = "calorie",
387
388    -- synonyms
389
390    Cal                         = "calorie",
391
392}
393
394local long_operators = {
395
396    Times   = "times",
397    Solidus = "solidus",
398    Per     = "per",
399    OutOf   = "outof",
400
401}
402
403local long_suffixes = {
404
405    Linear     = "linear",
406    Square     = "square",
407    Cubic      = "cubic",
408    Quadratic  = "quadratic",
409    Inverse    = "inverse",
410    ILinear    = "ilinear",
411    ISquare    = "isquare",
412    ICubic     = "icubic",
413    IQuadratic = "iquadratic",
414
415}
416
417local short_prefixes = {
418
419    y  = "yocto",
420    z  = "zetto",
421    a  = "atto",
422    f  = "femto",
423    p  = "pico",
424    n  = "nano",
425    u  = "micro",
426    m  = "milli",
427    c  = "centi",
428    d  = "deci",
429    da = "deca",
430    h  = "hecto",
431    k  = "kilo",
432    M  = "mega",
433    G  = "giga",
434    T  = "tera",
435    P  = "peta",
436    E  = "exa",
437    Z  = "zetta",
438    Y  = "yotta",
439
440}
441
442local short_units = { -- I'm not sure about casing
443
444    m  = "meter",
445    Hz = "hertz",
446    hz = "hertz",
447    B  = "bel",
448    b  = "bel",
449    lx = "lux",
450 -- da = "dalton",
451    h  = "hour",
452    s  = "second",
453    g  = "gram",
454    n  = "newton",
455    V  = "volt",
456    t  = "tonne",
457    l  = "liter",
458 -- w  = "watt",
459    W  = "watt",
460 -- a  = "ampere",
461    A  = "ampere",
462
463    Ω  = "ohm",
464
465--  C  = "coulomb", -- needs checking with (c)enti
466--  K  = "kelvin",  -- needs checking with (k)ilo
467--  N  = "newton",  -- needs checking with (n)ewton
468
469    min = "minute",
470
471    [utfchar(0x2103)] = "celsius",
472    [utfchar(0x2109)] = "fahrenheit",
473}
474
475local short_operators = {
476    ["."] = "times",
477    ["*"] = "times",
478    ["/"] = "solidus",
479    [":"] = "outof",
480}
481
482local short_suffixes = { -- maybe just raw digit match
483    ["1"]   = "linear",
484    ["2"]   = "square",
485    ["3"]   = "cubic",
486    ["4"]   = "quadratic",
487    ["+1"]  = "linear",
488    ["+2"]  = "square",
489    ["+3"]  = "cubic",
490    ["+4"]  = "quadratic",
491    ["-1"]  = "inverse",
492    ["-1"]  = "ilinear",
493    ["-2"]  = "isquare",
494    ["-3"]  = "icubic",
495    ["-4"]  = "iquadratic",
496    ["^1"]  = "linear",
497    ["^2"]  = "square",
498    ["^3"]  = "cubic",
499    ["^4"]  = "quadratic",
500    ["^+1"] = "linear",
501    ["^+2"] = "square",
502    ["^+3"] = "cubic",
503    ["^+4"] = "quadratic",
504    ["^-1"] = "inverse",
505    ["^-1"] = "ilinear",
506    ["^-2"] = "isquare",
507    ["^-3"] = "icubic",
508    ["^-4"] = "iquadratic",
509}
510
511local symbol_units = {
512    Degrees    = "degree",
513    Degree     = "degree",
514 -- Deg        = "degree",
515    ["°"]      = "degree",
516    ArcMinute  = "arcminute",
517    [""]      = "arcminute", -- 0x2032
518    ArcSecond  = "arcsecond",
519    [""]      = "arcsecond", -- 0x2033
520    Percent    = "percent",
521    ["%"]      = "percent",
522    Promille   = "permille",
523    Permille   = "permille",
524}
525
526local packaged_units = {
527    Micron = "micron",
528    mmHg   = "millimetermercury",
529}
530
531-- rendering:
532
533local ctx_unitsPUS    = context.unitsPUS
534local ctx_unitsPU     = context.unitsPU
535local ctx_unitsPS     = context.unitsPS
536local ctx_unitsP      = context.unitsP
537local ctx_unitsUS     = context.unitsUS
538local ctx_unitsU      = context.unitsU
539local ctx_unitsS      = context.unitsS
540local ctx_unitsO      = context.unitsO
541local ctx_unitsN      = context.unitsN
542local ctx_unitsC      = context.unitsC
543local ctx_unitsQ      = context.unitsQ
544local ctx_unitsRPM    = context.unitsRPM
545local ctx_unitsRTO    = context.unitsRTO
546local ctx_unitsRabout = context.unitsRabout
547local ctx_unitsNstart = context.unitsNstart
548local ctx_unitsNstop  = context.unitsNstop
549local ctx_unitsNspace = context.unitsNspace
550local ctx_unitsPopen  = context.unitsPopen
551local ctx_unitsPclose = context.unitsPclose
552
553local labels = languages.data.labels
554
555labels.prefixes = allocate {
556    yocto = { labels = { en = [[y]]   } }, -- 10^{-24}
557    zepto = { labels = { en = [[z]]   } }, -- 10^{-21}
558    atto  = { labels = { en = [[a]]   } }, -- 10^{-18}
559    femto = { labels = { en = [[f]]   } }, -- 10^{-15}
560    pico  = { labels = { en = [[p]]   } }, -- 10^{-12}
561    nano  = { labels = { en = [[n]]   } }, -- 10^{-9}
562    micro = { labels = { en = [[\mu]] } }, -- 10^{-6}
563    milli = { labels = { en = [[m]]   } }, -- 10^{-3}
564    centi = { labels = { en = [[c]]   } }, -- 10^{-2}
565    deci  = { labels = { en = [[d]]   } }, -- 10^{-1}
566    deca  = { labels = { en = [[da]]  } }, -- 10^{1}
567    hecto = { labels = { en = [[h]]   } }, -- 10^{2}
568    kilo  = { labels = { en = [[k]]   } }, -- 10^{3}
569    mega  = { labels = { en = [[M]]   } }, -- 10^{6}
570    giga  = { labels = { en = [[G]]   } }, -- 10^{9}
571    tera  = { labels = { en = [[T]]   } }, -- 10^{12}
572    peta  = { labels = { en = [[P]]   } }, -- 10^{15}
573    exa   = { labels = { en = [[E]]   } }, -- 10^{18}
574    zetta = { labels = { en = [[Z]]   } }, -- 10^{21}
575    yotta = { labels = { en = [[Y]]   } }, -- 10^{24}
576    kibi  = { labels = { en = [[Ki]]  } }, -- 2^{10} (not ki)
577    mebi  = { labels = { en = [[Mi]]  } }, -- 2^{20}
578    gibi  = { labels = { en = [[Gi]]  } }, -- 2^{30}
579    tebi  = { labels = { en = [[Ti]]  } }, -- 2^{40}
580    pebi  = { labels = { en = [[Pi]]  } }, -- 2^{50}
581    exbi  = { labels = { en = [[Ei]]  } }, -- 2^{60}
582    zebi  = { labels = { en = [[Zi]]  } }, -- binary
583    yobi  = { labels = { en = [[Yi]]  } }, -- binary
584    micro = { labels = { en = [[µ]]   } }, -- 0x00B5 \textmu
585    root  = { labels = { en = [[]]   } }, -- 0x221A
586}
587
588labels.units = allocate {
589    meter                       = { labels = { en = [[m]]                        } },
590    gram                        = { labels = { en = [[g]]                        } }, -- strictly kg is the base unit
591    second                      = { labels = { en = [[s]]                        } },
592    ampere                      = { labels = { en = [[A]]                        } },
593    kelvin                      = { labels = { en = [[K]]                        } },
594    mole                        = { labels = { en = [[mol]]                      } },
595    candela                     = { labels = { en = [[cd]]                       } },
596    mol                         = { labels = { en = [[mol]]                      } },
597    radian                      = { labels = { en = [[rad]]                      } },
598    steradian                   = { labels = { en = [[sr]]                       } },
599    hertz                       = { labels = { en = [[Hz]]                       } },
600    newton                      = { labels = { en = [[N]]                        } },
601    pascal                      = { labels = { en = [[Pa]]                       } },
602    joule                       = { labels = { en = [[J]]                        } },
603    watt                        = { labels = { en = [[W]]                        } },
604    coulomb                     = { labels = { en = [[C]]                        } },
605    volt                        = { labels = { en = [[V]]                        } },
606    farad                       = { labels = { en = [[F]]                        } },
607    ohm                         = { labels = { en = [[]]                        } }, -- 0x2126 \textohm
608    siemens                     = { labels = { en = [[S]]                        } },
609    weber                       = { labels = { en = [[Wb]]                       } },
610    mercury                     = { labels = { en = [[Hg]]                       } },
611    millimetermercury           = { labels = { en = [[mmHg]]                     } }, -- connected
612    tesla                       = { labels = { en = [[T]]                        } },
613    henry                       = { labels = { en = [[H]]                        } },
614    celsius                     = { labels = { en = [[\checkedtextcelsius]]      } }, -- 0x2103
615    lumen                       = { labels = { en = [[lm]]                       } },
616    lux                         = { labels = { en = [[lx]]                       } },
617    becquerel                   = { labels = { en = [[Bq]]                       } },
618    gray                        = { labels = { en = [[Gy]]                       } },
619    sievert                     = { labels = { en = [[Sv]]                       } },
620    katal                       = { labels = { en = [[kat]]                      } },
621    minute                      = { labels = { en = [[min]]                      } },
622    hour                        = { labels = { en = [[h]]                        } },
623    day                         = { labels = { en = [[d]]                        } },
624    gon                         = { labels = { en = [[gon]]                      } },
625    grad                        = { labels = { en = [[grad]]                     } },
626    hectare                     = { labels = { en = [[ha]]                       } },
627    liter                       = { labels = { en = [[l]]                        } }, -- symbol l or L
628    tonne                       = { labels = { en = [[t]]                        } },
629    electronvolt                = { labels = { en = [[eV]]                       } },
630    dalton                      = { labels = { en = [[Da]]                       } },
631    atomicmassunit              = { labels = { en = [[u]]                        } },
632    astronomicalunit            = { labels = { en = [[au]]                       } },
633    bar                         = { labels = { en = [[bar]]                      } },
634    angstrom                    = { labels = { en = [[Å]]                        } }, -- strictly Ångström
635    nauticalmile                = { labels = { en = [[M]]                        } },
636    barn                        = { labels = { en = [[b]]                        } },
637    knot                        = { labels = { en = [[kn]]                       } },
638    neper                       = { labels = { en = [[Np]]                       } },
639    bel                         = { labels = { en = [[B]]                        } }, -- in practice only decibel used
640    erg                         = { labels = { en = [[erg]]                      } },
641    dyne                        = { labels = { en = [[dyn]]                      } },
642    poise                       = { labels = { en = [[P]]                        } },
643    stokes                      = { labels = { en = [[St]]                       } },
644    stilb                       = { labels = { en = [[sb]]                       } },
645    phot                        = { labels = { en = [[phot]]                     } },
646    gal                         = { labels = { en = [[gal]]                      } },
647    maxwell                     = { labels = { en = [[Mx]]                       } },
648    gauss                       = { labels = { en = [[G]]                        } },
649    oersted                     = { labels = { en = [[Oe]]                       } }, -- strictly Œrsted
650    bit                         = { labels = { en = [[bit]]                      } },
651    byte                        = { labels = { en = [[B]]                        } },
652    baud                        = { labels = { en = [[Bd]]                       } },
653    erlang                      = { labels = { en = [[E]]                        } },
654    atmosphere                  = { labels = { en = [[atm]]                      } },
655    revolution                  = { labels = { en = [[rev]]                      } },
656    fahrenheit                  = { labels = { en = [[\checkedtextfahrenheit]]   } }, -- 0x2109
657    foot                        = { labels = { en = [[ft]]                       } },
658    inch                        = { labels = { en = [[inch]]                     } },
659    calorie                     = { labels = { en = [[cal]]                      } },
660    --
661    degree                      = { labels = { en = [[°]]} },
662    arcminute                   = { labels = { en = [[\checkedtextprime]]        } }, -- ′ 0x2032
663    arcsecond                   = { labels = { en = [[\checkedtextdoubleprime]]  } }, -- ″ 0x2033
664    percent                     = { labels = { en = [[\percent]]                 } },
665    permille                    = { labels = { en = [[\promille]]                } },
666    --
667    micron                      = { labels = { en = [[\textmu m]]                } },
668}
669
670labels.operators = allocate {
671    times   = { labels = { en = [[\unitsTIMES]]   } },
672    solidus = { labels = { en = [[\unitsSOLIDUS]] } },
673    per     = { labels = { en = [[\unitsSOLIDUS]] } },
674    outof   = { labels = { en = [[\unitsOUTOF]]   } },
675}
676
677labels.suffixes = allocate {
678    linear     = { labels = { en = [[1]]  } },
679    square     = { labels = { en = [[2]]  } },
680    cubic      = { labels = { en = [[3]]  } },
681    quadratic  = { labels = { en = [[4]]  } },
682    inverse    = { labels = { en = [[\mathminus1]] } },
683    ilinear    = { labels = { en = [[\mathminus1]] } },
684    isquare    = { labels = { en = [[\mathminus2]] } },
685    icubic     = { labels = { en = [[\mathminus3]] } },
686    iquadratic = { labels = { en = [[\mathminus4]] } },
687}
688
689local function dimpus(p,u,s)
690    if trace_units then
691        report_units("prefix %a, unit %a, suffix %a",p,u,s)
692    end    --
693    if p ~= "" then
694        if u ~= ""  then
695            if s ~= ""  then
696                ctx_unitsPUS(p,u,s)
697            else
698                ctx_unitsPU(p,u)
699            end
700        elseif s ~= ""  then
701            ctx_unitsPS(p,s)
702        else
703            ctx_unitsP(p)
704        end
705    else
706        if u ~= ""  then
707            if s ~= ""  then
708                ctx_unitsUS(u,s)
709         -- elseif c then
710         --     ctx_unitsC(u)
711            else
712                ctx_unitsU(u)
713            end
714        elseif s ~= ""  then
715            ctx_unitsS(s)
716        else
717            ctx_unitsP(p)
718        end
719    end
720end
721
722local function dimspu(s,p,u)
723    return dimpus(p,u,s)
724end
725
726local function dimop(o)
727    if trace_units then
728        report_units("operator %a",o)
729    end
730    if o then
731        ctx_unitsO(o)
732    end
733end
734
735local function dimsym(s)
736    if trace_units then
737        report_units("symbol %a",s)
738    end
739    s = symbol_units[s] or s
740    if s then
741        ctx_unitsC(s)
742    end
743end
744
745local function dimpre(p)
746    if trace_units then
747        report_units("prefix [%a",p)
748    end
749    p = packaged_units[p] or p
750    if p then
751        ctx_unitsU(p)
752    end
753end
754
755-- patterns:
756--
757-- space inside Cs else funny captures and args to function
758--
759-- square centi meter per square kilo seconds
760
761-- todo 0x -> rm
762
763local function update_parsers() -- todo: don't remap utf sequences
764
765    local all_long_prefixes  = { }
766    local all_long_units     = { }
767    local all_long_operators = { }
768    local all_long_suffixes  = { }
769    local all_symbol_units   = { }
770    local all_packaged_units = { }
771
772    for k, v in next, long_prefixes  do all_long_prefixes [k] = v all_long_prefixes [lower(k)] = v end
773    for k, v in next, long_units     do all_long_units    [k] = v all_long_units    [lower(k)] = v end
774    for k, v in next, long_operators do all_long_operators[k] = v all_long_operators[lower(k)] = v end
775    for k, v in next, long_suffixes  do all_long_suffixes [k] = v all_long_suffixes [lower(k)] = v end
776    for k, v in next, symbol_units   do all_symbol_units  [k] = v all_symbol_units  [lower(k)] = v end
777    for k, v in next, packaged_units do all_packaged_units[k] = v all_packaged_units[lower(k)] = v end
778
779    local somespace        = P(" ")^0/""
780
781    local p_long_prefix    = appendlpeg(all_long_prefixes,nil,true)
782    local p_long_unit      = appendlpeg(all_long_units,nil,true)
783    local p_long_operator  = appendlpeg(all_long_operators,nil,true)
784    local p_long_suffix    = appendlpeg(all_long_suffixes,nil,true)
785    local p_symbol         = appendlpeg(all_symbol_units,nil,true)
786    local p_packaged       = appendlpeg(all_packaged_units,nil,true)
787
788    local p_short_prefix   = appendlpeg(short_prefixes)
789    local p_short_unit     = appendlpeg(short_units)
790    local p_short_operator = appendlpeg(short_operators)
791    local p_short_suffix   = appendlpeg(short_suffixes)
792
793    -- more efficient but needs testing
794
795--     local p_long_prefix    = utfchartabletopattern(all_long_prefixes)  / all_long_prefixes
796--     local p_long_unit      = utfchartabletopattern(all_long_units)     / all_long_units
797--     local p_long_operator  = utfchartabletopattern(all_long_operators) / all_long_operators
798--     local p_long_suffix    = utfchartabletopattern(all_long_suffixes)  / all_long_suffixes
799--     local p_symbol         = utfchartabletopattern(all_symbol_units)   / all_symbol_units
800--     local p_packaged       = utfchartabletopattern(all_packaged_units) / all_packaged_units
801
802--     local p_short_prefix   = utfchartabletopattern(short_prefixes)     / short_prefixes
803--     local p_short_unit     = utfchartabletopattern(short_units)        / short_units
804--     local p_short_operator = utfchartabletopattern(short_operators)    / short_operators
805--     local p_short_suffix   = utfchartabletopattern(short_suffixes)     / short_suffixes
806
807    -- we can can cleanup some space issues here (todo)
808
809    local unitparser = P { "unit",
810        --
811        longprefix    = Cs(V("somespace") * p_long_prefix),
812        shortprefix   = Cs(V("somespace") * p_short_prefix),
813        longsuffix    = Cs(V("somespace") * p_long_suffix),
814        shortsuffix   = Cs(V("somespace") * p_short_suffix),
815        shortunit     = Cs(V("somespace") * p_short_unit),
816        longunit      = Cs(V("somespace") * p_long_unit),
817        longoperator  = Cs(V("somespace") * p_long_operator),
818        shortoperator = Cs(V("somespace") * p_short_operator),
819        packaged      = Cs(V("somespace") * p_packaged),
820        --
821        nothing       = Cc(""),
822        somespace     = somespace,
823        nospace       = (1-somespace)^1, -- was 0
824     -- ignore        = P(-1),
825        --
826        qualifier     = Cs(V("somespace") * (lparent/"") * (1-rparent)^1 * (rparent/"")),
827        --
828        somesymbol    = V("somespace")
829                      * (p_symbol/dimsym)
830                      * V("somespace"),
831        somepackaged  = V("somespace")
832                      * (V("packaged") / dimpre)
833                      * V("somespace"),
834     -- someunknown   = V("somespace")
835     --               * (V("nospace")/ctx_unitsU)
836     --               * V("somespace"),
837        --
838        combination   = V("longprefix")  * V("longunit")   -- centi meter
839                      + V("nothing")     * V("longunit")
840                      + V("shortprefix") * V("shortunit")  -- c m
841                      + V("nothing")     * V("shortunit")
842                      + V("longprefix")  * V("shortunit")  -- centi m
843                      + V("shortprefix") * V("longunit"),  -- c meter
844
845--         combination   = (   V("longprefix")   -- centi meter
846--                           + V("nothing")
847--                         ) * V("longunit")
848--                       + (   V("shortprefix")  -- c m
849--                           + V("nothing")
850--                           + V("longprefix")
851--                         ) * V("shortunit")    -- centi m
852--                       + (   V("shortprefix")  -- c meter
853--                         ) * V("longunit"),
854
855
856        dimension     = V("somespace")
857                      * (
858                            V("packaged") / dimpre
859                          + (V("longsuffix") * V("combination")) / dimspu
860                          + (V("combination") * (V("shortsuffix") + V("nothing"))) / dimpus
861                        )
862                      * (V("qualifier") / ctx_unitsQ)^-1
863                      * V("somespace"),
864        operator      = V("somespace")
865                      * ((V("longoperator") + V("shortoperator")) / dimop)
866                      * V("somespace"),
867        snippet       = V("dimension")
868                      + V("somesymbol"),
869        unit          = (   V("snippet") * (V("operator") * V("snippet"))^0
870                          + V("somepackaged")
871                        )^1,
872    }
873
874    -- todo: avoid \ctx_unitsNstart\ctx_unitsNstop (weird that it can happen .. now catched at tex end)
875
876    local letter = R("az","AZ")
877    local bound  = #(1-letter)
878 -- local number = lpeg.patterns.number
879    local number = Cs( P("$")     * (1-P("$"))^1 * P("$")
880                     + P([[\m{]]) * (1-P("}"))^1 * P("}")
881                     + (1-letter-P(" "))^1 -- todo: catch { } -- not ok
882                   ) / ctx_unitsN
883
884    local start   = Cc(nil) / ctx_unitsNstart
885    local stop    = Cc(nil) / ctx_unitsNstop
886    local space   = P(" ") * Cc(nil) / ctx_unitsNspace
887    local open    = P("(") * Cc(nil) / ctx_unitsPopen
888    local close   = P(")") * Cc(nil) / ctx_unitsPclose
889
890    local range   = somespace
891                  * ( (P("±") + P("pm") * bound) / "" / ctx_unitsRPM
892                    + (P("") + P("to") * bound) / "" / ctx_unitsRTO )
893                  * somespace
894
895    local about   = (P("±") + P("pm") * bound) / "" / ctx_unitsRabout
896                  * somespace
897
898    -- todo: start / stop
899
900    local function combine(parser)
901        return P { "start",
902            number  = start * dleader * (parser + number) * stop,
903            anumber = space
904                    * open
905                    * V("about")^-1
906                    * V("number")
907                    * close,
908            rule    = V("number")^-1
909                    * (V("range") * V("number") + V("anumber"))^-1,
910            unit    = unitparser,
911            about   = about,
912            range   = range,
913            space   = space,
914            start   = V("rule")
915                    * V("unit")
916                    * (V("space") * V("rule") * V("unit"))^0
917                    + open
918                    * V("number")
919                    * (V("range") * V("number"))^-1
920                    * close
921                    * dtrailer^-1
922                    * V("unit")
923                    + V("number")
924        }
925    end
926
927    return combine(p_c_dparser), combine(c_p_dparser)
928end
929
930local p_c_parser = nil
931local c_p_parser = nil
932local dirty      = true
933
934local function makeunit(str,reverse)
935    if dirty then
936        if trace_units then
937            report_units("initializing parser")
938        end
939        p_c_parser, c_p_parser = update_parsers()
940        dirty = false
941    end
942    local ok
943    if reverse then
944        ok = matchlpeg(p_c_parser,str)
945    else
946        ok = matchlpeg(c_p_parser,str)
947    end
948    if not ok then
949        report_units("unable to parse: %s",str)
950        context(str)
951    end
952end
953
954local function trigger(t,k,v)
955    rawset(t,k,v)
956    dirty = true
957end
958
959local t_units = {
960    prefixes  = setmetatablenewindex(long_prefixes,trigger),
961    units     = setmetatablenewindex(long_units,trigger),
962    operators = setmetatablenewindex(long_operators,trigger),
963    suffixes  = setmetatablenewindex(long_suffixes,trigger),
964    symbols   = setmetatablenewindex(symbol_units,trigger),
965    packaged  = setmetatablenewindex(packaged_units,trigger),
966}
967
968local t_shortcuts = {
969    prefixes  = setmetatablenewindex(short_prefixes,trigger),
970    units     = setmetatablenewindex(short_units,trigger),
971    operators = setmetatablenewindex(short_operators,trigger),
972    suffixes  = setmetatablenewindex(short_suffixes,trigger),
973}
974
975physics.units.tables = allocate {
976    units     = t_units,
977    shortcuts = t_shortcuts,
978}
979
980local mapping = {
981    prefix   = "prefixes",
982    unit     = "units",
983    operator = "operators",
984    suffixe  = "suffixes",
985    symbol   = "symbols",
986    packaged = "packaged",
987}
988
989local function registerunit(category,list)
990    if not list or list == "" then
991        list = category
992        category = "unit"
993    end
994    local t = t_units[mapping[category]]
995    if t then
996        for k, v in next, utilities.parsers.settings_to_hash(list or "") do
997            t[k] = v
998        end
999    end
1000 -- inspect(tables)
1001end
1002
1003physics.units.registerunit = registerunit
1004
1005implement { name = "digits_normal",  actions = makedigits,   arguments = "string" }
1006implement { name = "digits_reverse", actions = makedigits,   arguments = { "string", true } }
1007implement { name = "unit_normal",    actions = makeunit,     arguments = "string"}
1008implement { name = "unit_reverse",   actions = makeunit,     arguments = { "string", true } }
1009implement { name = "registerunit",   actions = registerunit, arguments = "2 strings" }
1010
1011implement {
1012    name      = "hyphenateddigits",
1013    public    = true,
1014    protected = true,
1015    arguments = { "optional", "string" },
1016    actions   = function(filler, digits)
1017        digits = gsub(digits,"(%d)","%1\\digitsbreak ") -- space needed for following letters
1018        digits = gsub(digits,"\\-$",filler)
1019        context(digits)
1020    end
1021}
1022