phys-dim.lua /size: 38 Kb    last modification: 2021-10-28 13:50
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    min = "minute",
464
465    [utfchar(0x2103)] = "celsius",
466    [utfchar(0x2109)] = "fahrenheit",
467}
468
469local short_operators = {
470    ["."] = "times",
471    ["*"] = "times",
472    ["/"] = "solidus",
473    [":"] = "outof",
474}
475
476local short_suffixes = { -- maybe just raw digit match
477    ["1"]   = "linear",
478    ["2"]   = "square",
479    ["3"]   = "cubic",
480    ["4"]   = "quadratic",
481    ["+1"]  = "linear",
482    ["+2"]  = "square",
483    ["+3"]  = "cubic",
484    ["+4"]  = "quadratic",
485    ["-1"]  = "inverse",
486    ["-1"]  = "ilinear",
487    ["-2"]  = "isquare",
488    ["-3"]  = "icubic",
489    ["-4"]  = "iquadratic",
490    ["^1"]  = "linear",
491    ["^2"]  = "square",
492    ["^3"]  = "cubic",
493    ["^4"]  = "quadratic",
494    ["^+1"] = "linear",
495    ["^+2"] = "square",
496    ["^+3"] = "cubic",
497    ["^+4"] = "quadratic",
498    ["^-1"] = "inverse",
499    ["^-1"] = "ilinear",
500    ["^-2"] = "isquare",
501    ["^-3"] = "icubic",
502    ["^-4"] = "iquadratic",
503}
504
505local symbol_units = {
506    Degrees    = "degree",
507    Degree     = "degree",
508 -- Deg        = "degree",
509    ["°"]      = "degree",
510    ArcMinute  = "arcminute",
511    [""]      = "arcminute", -- 0x2032
512    ArcSecond  = "arcsecond",
513    [""]      = "arcsecond", -- 0x2033
514    Percent    = "percent",
515    ["%"]      = "percent",
516    Promille   = "permille",
517    Permille   = "permille",
518}
519
520local packaged_units = {
521    Micron = "micron",
522    mmHg   = "millimetermercury",
523}
524
525-- rendering:
526
527local ctx_unitsPUS    = context.unitsPUS
528local ctx_unitsPU     = context.unitsPU
529local ctx_unitsPS     = context.unitsPS
530local ctx_unitsP      = context.unitsP
531local ctx_unitsUS     = context.unitsUS
532local ctx_unitsU      = context.unitsU
533local ctx_unitsS      = context.unitsS
534local ctx_unitsO      = context.unitsO
535local ctx_unitsN      = context.unitsN
536local ctx_unitsC      = context.unitsC
537local ctx_unitsQ      = context.unitsQ
538local ctx_unitsRPM    = context.unitsRPM
539local ctx_unitsRTO    = context.unitsRTO
540local ctx_unitsRabout = context.unitsRabout
541local ctx_unitsNstart = context.unitsNstart
542local ctx_unitsNstop  = context.unitsNstop
543local ctx_unitsNspace = context.unitsNspace
544local ctx_unitsPopen  = context.unitsPopen
545local ctx_unitsPclose = context.unitsPclose
546
547local labels = languages.data.labels
548
549labels.prefixes = allocate {
550    yocto = { labels = { en = [[y]]   } }, -- 10^{-24}
551    zepto = { labels = { en = [[z]]   } }, -- 10^{-21}
552    atto  = { labels = { en = [[a]]   } }, -- 10^{-18}
553    femto = { labels = { en = [[f]]   } }, -- 10^{-15}
554    pico  = { labels = { en = [[p]]   } }, -- 10^{-12}
555    nano  = { labels = { en = [[n]]   } }, -- 10^{-9}
556    micro = { labels = { en = [[\mu]] } }, -- 10^{-6}
557    milli = { labels = { en = [[m]]   } }, -- 10^{-3}
558    centi = { labels = { en = [[c]]   } }, -- 10^{-2}
559    deci  = { labels = { en = [[d]]   } }, -- 10^{-1}
560    deca  = { labels = { en = [[da]]  } }, -- 10^{1}
561    hecto = { labels = { en = [[h]]   } }, -- 10^{2}
562    kilo  = { labels = { en = [[k]]   } }, -- 10^{3}
563    mega  = { labels = { en = [[M]]   } }, -- 10^{6}
564    giga  = { labels = { en = [[G]]   } }, -- 10^{9}
565    tera  = { labels = { en = [[T]]   } }, -- 10^{12}
566    peta  = { labels = { en = [[P]]   } }, -- 10^{15}
567    exa   = { labels = { en = [[E]]   } }, -- 10^{18}
568    zetta = { labels = { en = [[Z]]   } }, -- 10^{21}
569    yotta = { labels = { en = [[Y]]   } }, -- 10^{24}
570    kibi  = { labels = { en = [[Ki]]  } }, -- 2^{10} (not ki)
571    mebi  = { labels = { en = [[Mi]]  } }, -- 2^{20}
572    gibi  = { labels = { en = [[Gi]]  } }, -- 2^{30}
573    tebi  = { labels = { en = [[Ti]]  } }, -- 2^{40}
574    pebi  = { labels = { en = [[Pi]]  } }, -- 2^{50}
575    exbi  = { labels = { en = [[Ei]]  } }, -- 2^{60}
576    zebi  = { labels = { en = [[Zi]]  } }, -- binary
577    yobi  = { labels = { en = [[Yi]]  } }, -- binary
578    micro = { labels = { en = [[µ]]   } }, -- 0x00B5 \textmu
579    root  = { labels = { en = [[]]   } }, -- 0x221A
580}
581
582labels.units = allocate {
583    meter                       = { labels = { en = [[m]]                        } },
584    gram                        = { labels = { en = [[g]]                        } }, -- strictly kg is the base unit
585    second                      = { labels = { en = [[s]]                        } },
586    ampere                      = { labels = { en = [[A]]                        } },
587    kelvin                      = { labels = { en = [[K]]                        } },
588    mole                        = { labels = { en = [[mol]]                      } },
589    candela                     = { labels = { en = [[cd]]                       } },
590    mol                         = { labels = { en = [[mol]]                      } },
591    radian                      = { labels = { en = [[rad]]                      } },
592    steradian                   = { labels = { en = [[sr]]                       } },
593    hertz                       = { labels = { en = [[Hz]]                       } },
594    newton                      = { labels = { en = [[N]]                        } },
595    pascal                      = { labels = { en = [[Pa]]                       } },
596    joule                       = { labels = { en = [[J]]                        } },
597    watt                        = { labels = { en = [[W]]                        } },
598    coulomb                     = { labels = { en = [[C]]                        } },
599    volt                        = { labels = { en = [[V]]                        } },
600    farad                       = { labels = { en = [[F]]                        } },
601    ohm                         = { labels = { en = [[]]                        } }, -- 0x2126 \textohm
602    siemens                     = { labels = { en = [[S]]                        } },
603    weber                       = { labels = { en = [[Wb]]                       } },
604    mercury                     = { labels = { en = [[Hg]]                       } },
605    millimetermercury           = { labels = { en = [[mmHg]]                     } }, -- connected
606    tesla                       = { labels = { en = [[T]]                        } },
607    henry                       = { labels = { en = [[H]]                        } },
608    celsius                     = { labels = { en = [[\checkedtextcelsius]]      } }, -- 0x2103
609    lumen                       = { labels = { en = [[lm]]                       } },
610    lux                         = { labels = { en = [[lx]]                       } },
611    becquerel                   = { labels = { en = [[Bq]]                       } },
612    gray                        = { labels = { en = [[Gy]]                       } },
613    sievert                     = { labels = { en = [[Sv]]                       } },
614    katal                       = { labels = { en = [[kat]]                      } },
615    minute                      = { labels = { en = [[min]]                      } },
616    hour                        = { labels = { en = [[h]]                        } },
617    day                         = { labels = { en = [[d]]                        } },
618    gon                         = { labels = { en = [[gon]]                      } },
619    grad                        = { labels = { en = [[grad]]                     } },
620    hectare                     = { labels = { en = [[ha]]                       } },
621    liter                       = { labels = { en = [[l]]                        } }, -- symbol l or L
622    tonne                       = { labels = { en = [[t]]                        } },
623    electronvolt                = { labels = { en = [[eV]]                       } },
624    dalton                      = { labels = { en = [[Da]]                       } },
625    atomicmassunit              = { labels = { en = [[u]]                        } },
626    astronomicalunit            = { labels = { en = [[au]]                       } },
627    bar                         = { labels = { en = [[bar]]                      } },
628    angstrom                    = { labels = { en = [[Å]]                        } }, -- strictly Ångström
629    nauticalmile                = { labels = { en = [[M]]                        } },
630    barn                        = { labels = { en = [[b]]                        } },
631    knot                        = { labels = { en = [[kn]]                       } },
632    neper                       = { labels = { en = [[Np]]                       } },
633    bel                         = { labels = { en = [[B]]                        } }, -- in practice only decibel used
634    erg                         = { labels = { en = [[erg]]                      } },
635    dyne                        = { labels = { en = [[dyn]]                      } },
636    poise                       = { labels = { en = [[P]]                        } },
637    stokes                      = { labels = { en = [[St]]                       } },
638    stilb                       = { labels = { en = [[sb]]                       } },
639    phot                        = { labels = { en = [[phot]]                     } },
640    gal                         = { labels = { en = [[gal]]                      } },
641    maxwell                     = { labels = { en = [[Mx]]                       } },
642    gauss                       = { labels = { en = [[G]]                        } },
643    oersted                     = { labels = { en = [[Oe]]                       } }, -- strictly Œrsted
644    bit                         = { labels = { en = [[bit]]                      } },
645    byte                        = { labels = { en = [[B]]                        } },
646    baud                        = { labels = { en = [[Bd]]                       } },
647    erlang                      = { labels = { en = [[E]]                        } },
648    atmosphere                  = { labels = { en = [[atm]]                      } },
649    revolution                  = { labels = { en = [[rev]]                      } },
650    fahrenheit                  = { labels = { en = [[\checkedtextfahrenheit]]   } }, -- 0x2109
651    foot                        = { labels = { en = [[ft]]                       } },
652    inch                        = { labels = { en = [[inch]]                     } },
653    calorie                     = { labels = { en = [[cal]]                      } },
654    --
655    degree                      = { labels = { en = [[°]]} },
656    arcminute                   = { labels = { en = [[\checkedtextprime]]        } }, -- ′ 0x2032
657    arcsecond                   = { labels = { en = [[\checkedtextdoubleprime]]  } }, -- ″ 0x2033
658    percent                     = { labels = { en = [[\percent]]                 } },
659    permille                    = { labels = { en = [[\promille]]                } },
660    --
661    micron                      = { labels = { en = [[\textmu m]]                } },
662}
663
664labels.operators = allocate {
665    times   = { labels = { en = [[\unitsTIMES]]   } },
666    solidus = { labels = { en = [[\unitsSOLIDUS]] } },
667    per     = { labels = { en = [[\unitsSOLIDUS]] } },
668    outof   = { labels = { en = [[\unitsOUTOF]]   } },
669}
670
671labels.suffixes = allocate {
672    linear     = { labels = { en = [[1]]  } },
673    square     = { labels = { en = [[2]]  } },
674    cubic      = { labels = { en = [[3]]  } },
675    quadratic  = { labels = { en = [[4]]  } },
676    inverse    = { labels = { en = [[\mathminus1]] } },
677    ilinear    = { labels = { en = [[\mathminus1]] } },
678    isquare    = { labels = { en = [[\mathminus2]] } },
679    icubic     = { labels = { en = [[\mathminus3]] } },
680    iquadratic = { labels = { en = [[\mathminus4]] } },
681}
682
683local function dimpus(p,u,s)
684    if trace_units then
685        report_units("prefix %a, unit %a, suffix %a",p,u,s)
686    end    --
687    if p ~= "" then
688        if u ~= ""  then
689            if s ~= ""  then
690                ctx_unitsPUS(p,u,s)
691            else
692                ctx_unitsPU(p,u)
693            end
694        elseif s ~= ""  then
695            ctx_unitsPS(p,s)
696        else
697            ctx_unitsP(p)
698        end
699    else
700        if u ~= ""  then
701            if s ~= ""  then
702                ctx_unitsUS(u,s)
703         -- elseif c then
704         --     ctx_unitsC(u)
705            else
706                ctx_unitsU(u)
707            end
708        elseif s ~= ""  then
709            ctx_unitsS(s)
710        else
711            ctx_unitsP(p)
712        end
713    end
714end
715
716local function dimspu(s,p,u)
717    return dimpus(p,u,s)
718end
719
720local function dimop(o)
721    if trace_units then
722        report_units("operator %a",o)
723    end
724    if o then
725        ctx_unitsO(o)
726    end
727end
728
729local function dimsym(s)
730    if trace_units then
731        report_units("symbol %a",s)
732    end
733    s = symbol_units[s] or s
734    if s then
735        ctx_unitsC(s)
736    end
737end
738
739local function dimpre(p)
740    if trace_units then
741        report_units("prefix [%a",p)
742    end
743    p = packaged_units[p] or p
744    if p then
745        ctx_unitsU(p)
746    end
747end
748
749-- patterns:
750--
751-- space inside Cs else funny captures and args to function
752--
753-- square centi meter per square kilo seconds
754
755-- todo 0x -> rm
756
757local function update_parsers() -- todo: don't remap utf sequences
758
759    local all_long_prefixes  = { }
760    local all_long_units     = { }
761    local all_long_operators = { }
762    local all_long_suffixes  = { }
763    local all_symbol_units   = { }
764    local all_packaged_units = { }
765
766    for k, v in next, long_prefixes  do all_long_prefixes [k] = v all_long_prefixes [lower(k)] = v end
767    for k, v in next, long_units     do all_long_units    [k] = v all_long_units    [lower(k)] = v end
768    for k, v in next, long_operators do all_long_operators[k] = v all_long_operators[lower(k)] = v end
769    for k, v in next, long_suffixes  do all_long_suffixes [k] = v all_long_suffixes [lower(k)] = v end
770    for k, v in next, symbol_units   do all_symbol_units  [k] = v all_symbol_units  [lower(k)] = v end
771    for k, v in next, packaged_units do all_packaged_units[k] = v all_packaged_units[lower(k)] = v end
772
773    local somespace        = P(" ")^0/""
774
775    local p_long_prefix    = appendlpeg(all_long_prefixes,nil,true)
776    local p_long_unit      = appendlpeg(all_long_units,nil,true)
777    local p_long_operator  = appendlpeg(all_long_operators,nil,true)
778    local p_long_suffix    = appendlpeg(all_long_suffixes,nil,true)
779    local p_symbol         = appendlpeg(all_symbol_units,nil,true)
780    local p_packaged       = appendlpeg(all_packaged_units,nil,true)
781
782    local p_short_prefix   = appendlpeg(short_prefixes)
783    local p_short_unit     = appendlpeg(short_units)
784    local p_short_operator = appendlpeg(short_operators)
785    local p_short_suffix   = appendlpeg(short_suffixes)
786
787    -- more efficient but needs testing
788
789--     local p_long_prefix    = utfchartabletopattern(all_long_prefixes)  / all_long_prefixes
790--     local p_long_unit      = utfchartabletopattern(all_long_units)     / all_long_units
791--     local p_long_operator  = utfchartabletopattern(all_long_operators) / all_long_operators
792--     local p_long_suffix    = utfchartabletopattern(all_long_suffixes)  / all_long_suffixes
793--     local p_symbol         = utfchartabletopattern(all_symbol_units)   / all_symbol_units
794--     local p_packaged       = utfchartabletopattern(all_packaged_units) / all_packaged_units
795
796--     local p_short_prefix   = utfchartabletopattern(short_prefixes)     / short_prefixes
797--     local p_short_unit     = utfchartabletopattern(short_units)        / short_units
798--     local p_short_operator = utfchartabletopattern(short_operators)    / short_operators
799--     local p_short_suffix   = utfchartabletopattern(short_suffixes)     / short_suffixes
800
801    -- we can can cleanup some space issues here (todo)
802
803    local unitparser = P { "unit",
804        --
805        longprefix    = Cs(V("somespace") * p_long_prefix),
806        shortprefix   = Cs(V("somespace") * p_short_prefix),
807        longsuffix    = Cs(V("somespace") * p_long_suffix),
808        shortsuffix   = Cs(V("somespace") * p_short_suffix),
809        shortunit     = Cs(V("somespace") * p_short_unit),
810        longunit      = Cs(V("somespace") * p_long_unit),
811        longoperator  = Cs(V("somespace") * p_long_operator),
812        shortoperator = Cs(V("somespace") * p_short_operator),
813        packaged      = Cs(V("somespace") * p_packaged),
814        --
815        nothing       = Cc(""),
816        somespace     = somespace,
817        nospace       = (1-somespace)^1, -- was 0
818     -- ignore        = P(-1),
819        --
820        qualifier     = Cs(V("somespace") * (lparent/"") * (1-rparent)^1 * (rparent/"")),
821        --
822        somesymbol    = V("somespace")
823                      * (p_symbol/dimsym)
824                      * V("somespace"),
825        somepackaged  = V("somespace")
826                      * (V("packaged") / dimpre)
827                      * V("somespace"),
828     -- someunknown   = V("somespace")
829     --               * (V("nospace")/ctx_unitsU)
830     --               * V("somespace"),
831        --
832        combination   = V("longprefix")  * V("longunit")   -- centi meter
833                      + V("nothing")     * V("longunit")
834                      + V("shortprefix") * V("shortunit")  -- c m
835                      + V("nothing")     * V("shortunit")
836                      + V("longprefix")  * V("shortunit")  -- centi m
837                      + V("shortprefix") * V("longunit"),  -- c meter
838
839--         combination   = (   V("longprefix")   -- centi meter
840--                           + V("nothing")
841--                         ) * V("longunit")
842--                       + (   V("shortprefix")  -- c m
843--                           + V("nothing")
844--                           + V("longprefix")
845--                         ) * V("shortunit")    -- centi m
846--                       + (   V("shortprefix")  -- c meter
847--                         ) * V("longunit"),
848
849
850        dimension     = V("somespace")
851                      * (
852                            V("packaged") / dimpre
853                          + (V("longsuffix") * V("combination")) / dimspu
854                          + (V("combination") * (V("shortsuffix") + V("nothing"))) / dimpus
855                        )
856                      * (V("qualifier") / ctx_unitsQ)^-1
857                      * V("somespace"),
858        operator      = V("somespace")
859                      * ((V("longoperator") + V("shortoperator")) / dimop)
860                      * V("somespace"),
861        snippet       = V("dimension")
862                      + V("somesymbol"),
863        unit          = (   V("snippet") * (V("operator") * V("snippet"))^0
864                          + V("somepackaged")
865                        )^1,
866    }
867
868    -- todo: avoid \ctx_unitsNstart\ctx_unitsNstop (weird that it can happen .. now catched at tex end)
869
870    local letter = R("az","AZ")
871    local bound  = #(1-letter)
872 -- local number = lpeg.patterns.number
873    local number = Cs( P("$")     * (1-P("$"))^1 * P("$")
874                     + P([[\m{]]) * (1-P("}"))^1 * P("}")
875                     + (1-letter-P(" "))^1 -- todo: catch { } -- not ok
876                   ) / ctx_unitsN
877
878    local start   = Cc(nil) / ctx_unitsNstart
879    local stop    = Cc(nil) / ctx_unitsNstop
880    local space   = P(" ") * Cc(nil) / ctx_unitsNspace
881    local open    = P("(") * Cc(nil) / ctx_unitsPopen
882    local close   = P(")") * Cc(nil) / ctx_unitsPclose
883
884    local range   = somespace
885                  * ( (P("±") + P("pm") * bound) / "" / ctx_unitsRPM
886                    + (P("") + P("to") * bound) / "" / ctx_unitsRTO )
887                  * somespace
888
889    local about   = (P("±") + P("pm") * bound) / "" / ctx_unitsRabout
890                  * somespace
891
892    -- todo: start / stop
893
894    local function combine(parser)
895        return P { "start",
896            number  = start * dleader * (parser + number) * stop,
897            anumber = space
898                    * open
899                    * V("about")^-1
900                    * V("number")
901                    * close,
902            rule    = V("number")^-1
903                    * (V("range") * V("number") + V("anumber"))^-1,
904            unit    = unitparser,
905            about   = about,
906            range   = range,
907            space   = space,
908            start   = V("rule")
909                    * V("unit")
910                    * (V("space") * V("rule") * V("unit"))^0
911                    + open
912                    * V("number")
913                    * (V("range") * V("number"))^-1
914                    * close
915                    * dtrailer^-1
916                    * V("unit")
917                    + V("number")
918        }
919    end
920
921    return combine(p_c_dparser), combine(c_p_dparser)
922end
923
924local p_c_parser = nil
925local c_p_parser = nil
926local dirty      = true
927
928local function makeunit(str,reverse)
929    if dirty then
930        if trace_units then
931            report_units("initializing parser")
932        end
933        p_c_parser, c_p_parser = update_parsers()
934        dirty = false
935    end
936    local ok
937    if reverse then
938        ok = matchlpeg(p_c_parser,str)
939    else
940        ok = matchlpeg(c_p_parser,str)
941    end
942    if not ok then
943        report_units("unable to parse: %s",str)
944        context(str)
945    end
946end
947
948local function trigger(t,k,v)
949    rawset(t,k,v)
950    dirty = true
951end
952
953local t_units = {
954    prefixes  = setmetatablenewindex(long_prefixes,trigger),
955    units     = setmetatablenewindex(long_units,trigger),
956    operators = setmetatablenewindex(long_operators,trigger),
957    suffixes  = setmetatablenewindex(long_suffixes,trigger),
958    symbols   = setmetatablenewindex(symbol_units,trigger),
959    packaged  = setmetatablenewindex(packaged_units,trigger),
960}
961
962local t_shortcuts = {
963    prefixes  = setmetatablenewindex(short_prefixes,trigger),
964    units     = setmetatablenewindex(short_units,trigger),
965    operators = setmetatablenewindex(short_operators,trigger),
966    suffixes  = setmetatablenewindex(short_suffixes,trigger),
967}
968
969physics.units.tables = allocate {
970    units     = t_units,
971    shortcuts = t_shortcuts,
972}
973
974local mapping = {
975    prefix   = "prefixes",
976    unit     = "units",
977    operator = "operators",
978    suffixe  = "suffixes",
979    symbol   = "symbols",
980    packaged = "packaged",
981}
982
983local function registerunit(category,list)
984    if not list or list == "" then
985        list = category
986        category = "unit"
987    end
988    local t = t_units[mapping[category]]
989    if t then
990        for k, v in next, utilities.parsers.settings_to_hash(list or "") do
991            t[k] = v
992        end
993    end
994 -- inspect(tables)
995end
996
997physics.units.registerunit = registerunit
998
999implement { name = "digits_normal",  actions = makedigits,   arguments = "string" }
1000implement { name = "digits_reverse", actions = makedigits,   arguments = { "string", true } }
1001implement { name = "unit_normal",    actions = makeunit,     arguments = "string"}
1002implement { name = "unit_reverse",   actions = makeunit,     arguments = { "string", true } }
1003implement { name = "registerunit",   actions = registerunit, arguments = "2 strings" }
1004
1005implement {
1006    name      = "hyphenateddigits",
1007    public    = true,
1008    protected = true,
1009    arguments = { "optional", "string" },
1010    actions   = function(filler, digits)
1011        digits = gsub(digits,"(%d)","%1\\digitsbreak")
1012        digits = gsub(digits,"\\-$",filler)
1013        context(digits)
1014    end
1015}
1016