--- /dev/null
+local ok, bit = pcall(require, "bit32")
+if not ok then
+ bit = require("bit") -- LuaJIT fallback
+end
+
+local M = {}
+
+local pluginDir = debug.getinfo(1, "S").source:sub(2):match("(.*/)")
+local csvPath = pluginDir .. "set-classes.csv"
+local csvLoaded = false
+local lookup = {}
+
+local function load_csv()
+ local f = io.open(csvPath, "r")
+ if not f then error("Could not open setclasses.csv at " .. csvPath) end
+ for line in f:lines() do
+ local dec, setClass = line:match("^(%d+),%[?([0-9AB ]+)%]?")
+ if dec and setClass then
+ lookup[tonumber(dec)] = "[" .. setClass .. "]"
+ end
+ end
+ f:close()
+end
+
+function M.get_visual_selection()
+ local start_pos = vim.fn.getpos("'<")
+ local end_pos = vim.fn.getpos("'>")
+ local lines = vim.fn.getline(start_pos[2], end_pos[2])
+ if #lines == 0 then return "" end
+ if #lines == 1 then
+ return string.sub(lines[1], start_pos[3], end_pos[3])
+ end
+ lines[1] = string.sub(lines[1], start_pos[3])
+ lines[#lines] = string.sub(lines[#lines], 1, end_pos[3])
+ return table.concat(lines, "\n")
+end
+
+function M.analyze_set(inputStr)
+ if not csvLoaded then
+ load_csv()
+ csvLoaded = true
+ end
+
+ local function parse_set(input)
+ local pcs = {}
+ for i = 1, #input do
+ local char = input:sub(i, i):upper()
+ local pc = tonumber(char)
+ if char == 'A' then pc = 10
+ elseif char == 'B' then pc = 11 end
+ if pc and pc >= 0 and pc < 12 then
+ table.insert(pcs, pc)
+ else
+ error("Invalid pc: " .. char)
+ end
+ end
+ return pcs
+ end
+
+ local function compute_bitmask(pcs)
+ local mask = 0
+ for _, pc in ipairs(pcs) do
+ mask = bit.bor(mask, bit.lshift(1, pc))
+ end
+ return mask
+ end
+
+ local function complement(mask)
+ return bit.band(bit.bnot(mask), 0xFFF)
+ end
+
+ local function multiply_set(pcs, factor)
+ local result = {}
+ for _, pc in ipairs(pcs) do
+ table.insert(result, (pc * factor) % 12)
+ end
+ return result
+ end
+
+ local pcs = parse_set(inputStr)
+ local mask = compute_bitmask(pcs)
+ local decimal = mask
+ local setClass = lookup[decimal] or "[Unknown]"
+ local complementMask = complement(mask)
+ local complementClass = lookup[complementMask] or "[Unknown]"
+ local m5Set = multiply_set(pcs, 5)
+ local m5Mask = compute_bitmask(m5Set)
+ local m5Decimal = m5Mask
+ local m5Class = lookup[m5Decimal] or "[Unknown]"
+
+ return {
+ input = inputStr,
+ decimal = decimal,
+ set_class = setClass,
+ complement_class = complementClass,
+ m5_class = m5Class
+ }
+end
+
+return M