Download
71 lines 2.4 KiBMoonScriptModified 2022-09-25 raw
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
aliases = {}

canonicalize = (str)->
    scale = 1.0
    units = {}
    for div,u,exp in str\gmatch("(%/?)[ ]*(%w+)%^?(%-?%d*)")
        exp = (not exp or exp == "") and 1 or tonumber(exp)
        if div != "" then exp = -exp
        alias = aliases[u]
        if alias
            for u2,exp2 in pairs(alias.units)
                scale = exp < 0 and scale / alias.amount or scale * alias.amount
                units[u2] = (units[u2] or 0) + exp*exp2
        else
            units[u] = (units[u] or 0) + exp

    units_sorted = [u for u,exp in pairs(units)]
    table.sort units_sorted, (a,b)->
        if units[a] == units[b]
            return a < b
        else
            return units[a] > units[b]

    chunks = {}
    for _,u in ipairs(units_sorted)
        exp = units[u]
        if exp == 1
            table.insert(chunks, u)
        elseif exp == -1
            table.insert(chunks, (("/%s")\format(u)))
        elseif exp < 0
            table.insert(chunks, (("/%s^%d")\format(u, -exp)))
        elseif exp > 0
            table.insert(chunks, (("%s^%d")\format(u, exp)))
    return table.concat(chunks, " ")\gsub(" /","/"), units, scale

class Measure
    new: (amount, str)=>
        str = str or ""
        @str, @units, scale = canonicalize(str)
        @amount = amount * scale
    __add: (other)=>
        assert(@str == other.str, "Incompatible units")
        return Measure(@amount + other.amount, @str)
    __sub: (other)=>
        assert(@str == other.str, "Incompatible units")
        return Measure(@amount - other.amount, @str)
    __mul: (other)=>
        if type(self) == 'number'
            return Measure(self * other.amount, other.str)
        elseif type(other) == 'number'
            return Measure(other * @amount, @str)
        return Measure(@amount * other.amount, @str.." "..other.str)
    __div: (other)=>
        if type(other) == 'number'
            return Measure(@amount / other, @str)
        return @ * other\invert()
    __tostring: =>
        if @str == ""
            return tostring(@amount)
        return ("%s<%s>")\format(@amount, @str)
    invert: =>
        chunks = [("%s^%d")\format(u, -exp) for u,exp in pairs(@units) when exp != 0]
        return Measure(1/@amount, table.concat(chunks, " "))
    in_units: (target)=> ("%s<%s>")\format(self/Measure(1, target), target)

return {
    :Measure,
    register_unit_alias: (alias_name, unit)->
        aliases[alias_name] = unit
}