Skip to content

Commit

Permalink
feat(augend): add decimal_fraction
Browse files Browse the repository at this point in the history
  • Loading branch information
monaqa committed Jun 11, 2023
1 parent 747d6fd commit 729bf07
Show file tree
Hide file tree
Showing 3 changed files with 379 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lua/dial/augend.lua
@@ -1,6 +1,7 @@
local case = require "dial.augend.case"
local constant = require "dial.augend.constant"
local date = require "dial.augend.date"
local decimal_fraction = require "dial.augend.decimal_fraction"
local hexcolor = require "dial.augend.hexcolor"
local integer = require "dial.augend.integer"
local semver = require "dial.augend.semver"
Expand All @@ -12,6 +13,7 @@ return {
case = case,
constant = constant,
date = date,
decimal_fraction = decimal_fraction,
hexcolor = hexcolor,
integer = integer,
semver = semver,
Expand Down
150 changes: 150 additions & 0 deletions lua/dial/augend/decimal_fraction.lua
@@ -0,0 +1,150 @@
local common = require "dial.augend.common"
local util = require "dial.util"

---@class AugendDecimalFraction
---@implement Augend
---@field signed boolean
---@field point_char string
local AugendDecimalFraction = {}

local M = {}

---@param config { signed?: boolean, point_char?: string }
---@return Augend
function M.new(config)
vim.validate {
signed = { config.signed, "boolean", true },
point_char = { config.point_char, "string", true },
}

local signed = util.unwrap_or(config.signed, false)
local point_char = util.unwrap_or(config.point_char, ".")
local digits_to_add = 0

return setmetatable({
signed = signed,
point_char = point_char,
digits_to_add = digits_to_add,
}, { __index = AugendDecimalFraction })
end

---@param line string
---@param cursor? integer
---@return textrange?
function AugendDecimalFraction:find(line, cursor)
local idx = 1
local integer_pattern
if self.signed then
integer_pattern = "%-?%d+"
else
integer_pattern = "%d+"
end
while idx <= #line do
local idx_integer_start, idx_integer_end = line:find(integer_pattern, idx)
if idx_integer_start == nil then
break
end

local result = (function()
idx = idx_integer_end + 1
-- invalid decimal fraction format
if line:sub(idx, idx) ~= self.point_char then
return -- continue while loop
end
idx = idx + 1
local idx_frac_start, idx_frac_end = line:find("^%d+", idx)
-- invalid decimal fraction format
if idx_frac_start == nil then
return -- continue while loop
end
idx = idx_frac_end + 1
-- decimal fraction before the cursor
if idx_frac_end < cursor then
return -- continue while loop
end

-- negative lookahead
if line:sub(idx, idx) == self.point_char then
return -- continue while loop
end
-- break loop and return value
return { from = idx_integer_start, to = idx_frac_end }
end)()

if result ~= nil then
return result
end
end
end

---@param line string
---@param cursor? integer
---@return textrange?
function AugendDecimalFraction:find_stateful(line, cursor)
local result = self:find(line, cursor)
if result == nil then
return nil
end

local point_pos = line:find(self.point_char, result.from, true)
if cursor < point_pos then
-- increment integer part
self.digits_to_add = 0
else
-- increment decimal part
self.digits_to_add = result.to - point_pos
end
return result
end

---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendDecimalFraction:add(text, addend, cursor)
local point_pos = text:find(self.point_char, 1, true)

local int_part = text:sub(1, point_pos - 1)
local frac_part = text:sub(point_pos + 1)

-- 桁数調整。元の数字が 12.3 なのに 0.01 を足したいとき、 12.31 になるようにする
if #frac_part < self.digits_to_add then
frac_part = frac_part .. ("0"):rep(self.digits_to_add - #frac_part)
end

local num = tonumber(int_part .. frac_part)
local add_num = addend * math.floor(10 ^ (#frac_part - self.digits_to_add))
num = num + add_num
if not self.signed and num < 0 then
num = 0
end
local str_num = tostring(num)

if num < 0 then
if #str_num - 1 <= #frac_part then
str_num = "-" .. ("0"):rep(#frac_part + 2 - #str_num) .. str_num:sub(2)
end
else
if #str_num <= #frac_part then
str_num = ("0"):rep(#frac_part + 1 - #str_num) .. str_num
end
end

-- pad as necessary
local new_int_part = str_num:sub(1, #str_num - #frac_part)
local new_dec_part = str_num:sub(#str_num - #frac_part + 1)

text = new_int_part .. "." .. new_dec_part
if self.digits_to_add == 0 then
-- incremented integer part
cursor = #new_int_part
else
cursor = #text
end

return { text = text, cursor = cursor }
end

M.alias = {}

return M
227 changes: 227 additions & 0 deletions tests/dial/augend/decimal_fraction_spec.lua
@@ -0,0 +1,227 @@
local decimal_fraction = require("dial.augend").decimal_fraction

describe("Test of decimal fraction { signed = false }", function()
local augend = decimal_fraction.new { signed = false }

describe("find function", function()
it("can find a decimal fraction", function()
assert.are.same(augend:find_stateful("273.15", 1), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("0.0", 1), { from = 1, to = 3 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("123 273.15", 1), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 1), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 5), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 7), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 3)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 10), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 3)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 11), { from = 12, to = 15 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 14), { from = 12, to = 15 })
assert.are.same(augend.digits_to_add, 1)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 16), nil)
end)

it("changes the value of digits_to_add appropriately depending on the cursor position", function()
assert.are.same(augend:find_stateful("273.15", 1), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("273.15", 3), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("273.15", 4), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 2)
assert.are.same(augend:find_stateful("273.15", 5), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 2)
assert.are.same(augend:find_stateful("273.15", 6), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 2)

assert.are.same(augend:find_stateful("9.80665", 1), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("9.80665", 3), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
assert.are.same(augend:find_stateful("9.80665", 4), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
assert.are.same(augend:find_stateful("9.80665", 5), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
assert.are.same(augend:find_stateful("9.80665", 6), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
end)

it("returns nil when no number appears", function()
assert.are.same(augend:find("", 1), nil)
assert.are.same(augend:find("foo bar", 1), nil)
end)
it("doesn't pick up negative number", function()
assert.are.same(augend:find("-42.195", 1), { from = 2, to = 7 })
assert.are.same(augend:find("fname-1.3", 1), { from = 7, to = 9 })
end)
it("doesn't pick up incomplete decimal fraction", function()
assert.are.same(augend:find("-42.", 1), nil)
assert.are.same(augend:find("fname-1.png", 1), nil)
assert.are.same(augend:find(".1", 1), nil)
end)
end)

describe("add function", function()
it("can increment integer parts", function()
augend.digits_to_add = 0
assert.are.same(augend:add("273.15", 1, 1), { text = "274.15", cursor = 3 })
assert.are.same(augend:add("273.15", 10, 1), { text = "283.15", cursor = 3 })
assert.are.same(augend:add("273.15", 10, 4), { text = "283.15", cursor = 3 })
assert.are.same(augend:add("273.15", 100, 1), { text = "373.15", cursor = 3 })
assert.are.same(augend:add("273.15", 1000, 1), { text = "1273.15", cursor = 4 })
assert.are.same(augend:add("273.15", -1, 1), { text = "272.15", cursor = 3 })
assert.are.same(augend:add("273.15", -10, 1), { text = "263.15", cursor = 3 })
assert.are.same(augend:add("273.15", -100, 1), { text = "173.15", cursor = 3 })
assert.are.same(augend:add("273.15", -200, 1), { text = "73.15", cursor = 2 })
assert.are.same(augend:add("273.15", -270, 1), { text = "3.15", cursor = 1 })
assert.are.same(augend:add("273.15", -273, 1), { text = "0.15", cursor = 1 })
assert.are.same(augend:add("273.15", -274, 1), { text = "0.00", cursor = 1 })
assert.are.same(augend:add("273.15", -1000, 1), { text = "0.00", cursor = 1 })
end)
it("can increment fragment parts", function()
augend.digits_to_add = 1
assert.are.same(augend:add("273.15", 1, 1), { text = "273.25", cursor = 6 })
assert.are.same(augend:add("273.15", 9, 1), { text = "274.05", cursor = 6 })
assert.are.same(augend:add("273.15", 10, 1), { text = "274.15", cursor = 6 })
assert.are.same(augend:add("273.15", 10, 4), { text = "274.15", cursor = 6 })
assert.are.same(augend:add("273.15", 100, 1), { text = "283.15", cursor = 6 })
assert.are.same(augend:add("273.15", 1000, 1), { text = "373.15", cursor = 6 })
assert.are.same(augend:add("273.15", 10000, 1), { text = "1273.15", cursor = 7 })
assert.are.same(augend:add("273.15", -1, 1), { text = "273.05", cursor = 6 })
assert.are.same(augend:add("273.15", -2, 1), { text = "272.95", cursor = 6 })
assert.are.same(augend:add("273.15", -10, 1), { text = "272.15", cursor = 6 })
assert.are.same(augend:add("273.15", -100, 1), { text = "263.15", cursor = 6 })
assert.are.same(augend:add("273.15", -1000, 1), { text = "173.15", cursor = 6 })
assert.are.same(augend:add("273.15", -10000, 1), { text = "0.00", cursor = 4 })

augend.digits_to_add = 2
assert.are.same(augend:add("273.15", 1, 1), { text = "273.16", cursor = 6 })
assert.are.same(augend:add("273.15", 5, 1), { text = "273.20", cursor = 6 })
assert.are.same(augend:add("273.15", 10, 1), { text = "273.25", cursor = 6 })
assert.are.same(augend:add("273.15", 90, 1), { text = "274.05", cursor = 6 })
assert.are.same(augend:add("273.15", 1000, 1), { text = "283.15", cursor = 6 })

augend.digits_to_add = 3
assert.are.same(augend:add("273.15", 1, 1), { text = "273.151", cursor = 7 })
assert.are.same(augend:add("273.15", 5, 1), { text = "273.155", cursor = 7 })
assert.are.same(augend:add("273.15", 10, 1), { text = "273.160", cursor = 7 })
end)
end)
end)

describe("Test of decimal fraction { signed = true }", function()
local augend = decimal_fraction.new { signed = true }

describe("find function", function()
it("can find a decimal fraction", function()
assert.are.same(augend:find_stateful("273.15", 1), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("0.0", 1), { from = 1, to = 3 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("123 273.15", 1), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 1), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 5), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 7), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 3)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 10), { from = 5, to = 10 })
assert.are.same(augend.digits_to_add, 3)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 11), { from = 12, to = 15 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 14), { from = 12, to = 15 })
assert.are.same(augend.digits_to_add, 1)
assert.are.same(augend:find_stateful("foo 42.195 28.3 bar", 16), nil)
end)

it("changes the value of digits_to_add appropriately depending on the cursor position", function()
assert.are.same(augend:find_stateful("273.15", 1), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("273.15", 3), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("273.15", 4), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 2)
assert.are.same(augend:find_stateful("273.15", 5), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 2)
assert.are.same(augend:find_stateful("273.15", 6), { from = 1, to = 6 })
assert.are.same(augend.digits_to_add, 2)

assert.are.same(augend:find_stateful("9.80665", 1), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 0)
assert.are.same(augend:find_stateful("9.80665", 3), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
assert.are.same(augend:find_stateful("9.80665", 4), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
assert.are.same(augend:find_stateful("9.80665", 5), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
assert.are.same(augend:find_stateful("9.80665", 6), { from = 1, to = 7 })
assert.are.same(augend.digits_to_add, 5)
end)

it("returns nil when no number appears", function()
assert.are.same(augend:find("", 1), nil)
assert.are.same(augend:find("foo bar", 1), nil)
end)
it("picks up negative number", function()
assert.are.same(augend:find("-42.195", 1), { from = 1, to = 7 })
assert.are.same(augend:find("fname-1.3", 1), { from = 6, to = 9 })
end)
it("doesn't pick up incomplete decimal fraction", function()
assert.are.same(augend:find("-42.", 1), nil)
assert.are.same(augend:find("fname-1.png", 1), nil)
assert.are.same(augend:find(".1", 1), nil)
end)
end)

describe("add function", function()
it("can increment integer parts", function()
augend.digits_to_add = 0
assert.are.same(augend:add("273.15", 1, 1), { text = "274.15", cursor = 3 })
assert.are.same(augend:add("273.15", 10, 1), { text = "283.15", cursor = 3 })
assert.are.same(augend:add("273.15", 10, 4), { text = "283.15", cursor = 3 })
assert.are.same(augend:add("273.15", 100, 1), { text = "373.15", cursor = 3 })
assert.are.same(augend:add("273.15", 1000, 1), { text = "1273.15", cursor = 4 })
assert.are.same(augend:add("273.15", -1, 1), { text = "272.15", cursor = 3 })
assert.are.same(augend:add("273.15", -10, 1), { text = "263.15", cursor = 3 })
assert.are.same(augend:add("273.15", -100, 1), { text = "173.15", cursor = 3 })
assert.are.same(augend:add("273.15", -200, 1), { text = "73.15", cursor = 2 })
assert.are.same(augend:add("273.15", -270, 1), { text = "3.15", cursor = 1 })
assert.are.same(augend:add("273.15", -273, 1), { text = "0.15", cursor = 1 })
assert.are.same(augend:add("273.15", -274, 1), { text = "-0.85", cursor = 2 })
assert.are.same(augend:add("273.15", -1000, 1), { text = "-726.85", cursor = 4 })
end)
it("can increment fragment parts", function()
augend.digits_to_add = 1
assert.are.same(augend:add("273.15", 1, 1), { text = "273.25", cursor = 6 })
assert.are.same(augend:add("273.15", 9, 1), { text = "274.05", cursor = 6 })
assert.are.same(augend:add("273.15", 10, 1), { text = "274.15", cursor = 6 })
assert.are.same(augend:add("273.15", 10, 4), { text = "274.15", cursor = 6 })
assert.are.same(augend:add("273.15", 100, 1), { text = "283.15", cursor = 6 })
assert.are.same(augend:add("273.15", 1000, 1), { text = "373.15", cursor = 6 })
assert.are.same(augend:add("273.15", 10000, 1), { text = "1273.15", cursor = 7 })
assert.are.same(augend:add("273.15", -1, 1), { text = "273.05", cursor = 6 })
assert.are.same(augend:add("273.15", -2, 1), { text = "272.95", cursor = 6 })
assert.are.same(augend:add("273.15", -10, 1), { text = "272.15", cursor = 6 })
assert.are.same(augend:add("273.15", -100, 1), { text = "263.15", cursor = 6 })
assert.are.same(augend:add("273.15", -1000, 1), { text = "173.15", cursor = 6 })
assert.are.same(augend:add("273.15", -10000, 1), { text = "-726.85", cursor = 7 })

augend.digits_to_add = 2
assert.are.same(augend:add("273.15", 1, 1), { text = "273.16", cursor = 6 })
assert.are.same(augend:add("273.15", 5, 1), { text = "273.20", cursor = 6 })
assert.are.same(augend:add("273.15", 10, 1), { text = "273.25", cursor = 6 })
assert.are.same(augend:add("273.15", 90, 1), { text = "274.05", cursor = 6 })
assert.are.same(augend:add("273.15", 1000, 1), { text = "283.15", cursor = 6 })

augend.digits_to_add = 3
assert.are.same(augend:add("273.15", 1, 1), { text = "273.151", cursor = 7 })
assert.are.same(augend:add("273.15", 5, 1), { text = "273.155", cursor = 7 })
assert.are.same(augend:add("273.15", 10, 1), { text = "273.160", cursor = 7 })
end)
end)
end)

0 comments on commit 729bf07

Please sign in to comment.