local function _tonumber (amount)
return SU.cast("number", amount)
end
local function _similarunit (a, b)
if type(b) == "number" or type(a) == "number" then
return true
else
return a.unit == b.unit
end
end
local function _hardnumber (a, b)
if type(b) == "number" or type(a) == "number" then
return true
else
return false
end
end
local function _unit (a, b)
return type(a) == "table" and a.unit or b.unit
end
local function _amount (input)
return type(input) == "number" and input or input.amount
end
local function _pt_amount (input)
return type(input) == "number" and input or not input and 0 or input._mutable and input.amount or input:tonumber()
end
local function _error_if_immutable (input)
if type(input) == "table" and not input._mutable then
SU.error("Not so fast, we can't do mutating arithmetic except on 'pt' unit measurements!", true)
end
end
local function _error_if_relative (a, b)
if type(a) == "table" and a.relative or type(b) == "table" and b.relative then
SU.error("Cannot do arithmetic on a relative measurement without explicitly absolutizing it.", true)
end
end
local measurement = pl.class({
type = "measurement",
amount = 0,
unit = "pt",
relative = false,
_mutable = false,
_init = function (self, amount, unit)
if unit then self.unit = unit end
if SU.type(amount) == "length" then
self.amount = amount.length.amount
self.unit = amount.length.unit
elseif type(amount) == "table" then
self.amount = amount.amount
self.unit = amount.unit
elseif type(tonumber(amount)) == "number" then
self.amount = tonumber(amount)
elseif type(amount) == "string" then
local parsed = SILE.parserBits.measurement:match(amount)
if not parsed then SU.error("Could not parse measurement '"..amount.."'") end
self.amount, self.unit = parsed.amount, parsed.unit
end
local _su = SILE.units[self.unit]
if not _su then SU.error("Unknown unit: " .. unit) end
self.relative = _su.relative
if self.unit == "pt" then self._mutable = true end
end,
absolute = function (self)
return SILE.measurement(self:tonumber())
end,
tostring = function (self)
return self:__tostring()
end,
tonumber = function (self)
local def = SILE.units[self.unit]
local amount = def.converter and def.converter(self.amount) or (self.amount * def.value)
return amount
end,
__tostring = function (self)
return self.amount .. self.unit
end,
__concat = function (a, b)
return tostring(a) .. tostring(b)
end,
__add = function (self, other)
if _similarunit(self, other) then
return SILE.measurement(_amount(self) + _amount(other), _unit(self, other))
else
_error_if_relative(self, other)
return SILE.measurement(_tonumber(self) + _tonumber(other))
end
end,
___add = function (self, other)
_error_if_immutable(self)
self.amount = self.amount + _pt_amount(other)
return nil
end,
__sub = function (self, other)
if _similarunit(self, other) then
return SILE.measurement(_amount(self) - _amount(other), _unit(self, other))
else
_error_if_relative(self, other)
return SILE.measurement(_tonumber(self) - _tonumber(other))
end
end,
___sub = function (self, other)
_error_if_immutable(self)
self.amount = self.amount - _pt_amount(other)
return nil
end,
__mul = function (self, other)
if _hardnumber(self, other) then
return SILE.measurement(_amount(self) * _amount(other), _unit(self, other))
else
_error_if_relative(self, other)
return SILE.measurement(_tonumber(self) * _tonumber(other))
end
end,
__pow = function (self, other)
if _hardnumber(self, other) then
return SILE.measurement(_amount(self) ^ _amount(other), self.unit)
else
_error_if_relative(self, other)
return SILE.measurement(_tonumber(self) ^ _tonumber(other))
end
end,
__div = function (self, other)
if _hardnumber(self, other) then
return SILE.measurement(_amount(self) / _amount(other), self.unit)
else
_error_if_relative(self, other)
return SILE.measurement(_tonumber(self) / _tonumber(other))
end
end,
__mod = function (self, other)
if _hardnumber(self, other) then
return SILE.measurement(_amount(self) % _amount(other), self.unit)
else
_error_if_relative(self, other)
return SILE.measurement(_tonumber(self) % _tonumber(other))
end
end,
__unm = function (self)
local ret = SILE.measurement(self)
ret.amount = self.amount * -1
return ret
end,
__eq = function (self, other)
return _tonumber(self) == _tonumber(other)
end,
__lt = function (self, other)
return _tonumber(self) < _tonumber(other)
end,
__le = function (self, other)
return _tonumber(self) <= _tonumber(other)
end
})
return measurement