Skip to content

Commit

Permalink
refactor(parser): extract util functions
Browse files Browse the repository at this point in the history
  • Loading branch information
theKnightsOfRohan committed Jan 26, 2025
1 parent 261b4b2 commit 4513e9c
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 163 deletions.
83 changes: 83 additions & 0 deletions lua/hexer/parse_utils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
local M = {}

---Integer in decimal form to binary string
---@param input integer
---@return string
function M.itob(input)
if input == 0 then
return "0"
end

if input < 0 then
input = math.abs(input)
end

local result = ""

while input > 0 do
local remainder = input % 2
result = remainder .. result
input = math.floor(input / 2)
end

return result
end

---Checks if the string has the given number format header and returns the start of the value if it does, otherwise 1
---@param str string
---@param header string[] the list of single-character format specifiers
---@return integer
function M.check_header(str, header)
for _, ch in ipairs(header) do
if str:sub(1, 1) == ch then
return 2
end

-- Should never occur, but just in case
if str:sub(1, 1) == '0' and str:sub(2, 2) == ch then
return 3
end
end

return 1
end

-- Check whether a passed string is wrapped by quotes
---@param str string
---@return boolean
function M.is_string_wrapped(str)
return #str >= 3 and (
(str:sub(1, 1) == '"' and str:sub(str:len()) == '"') or
(str:sub(1, 1) == "'" and str:sub(str:len()) == "'")
)
end

---String in decimal form to integer
---@param input string
---@return integer
function M.stoi(input)
return tonumber(input, 10);
end

---String in decimal form to integer
---@param input string
---@return integer
function M.xtoi(input)
return tonumber(input, 16);
end

---String in decimal form to integer
---@param input string
---@return integer
function M.btoi(input)
return tonumber(input, 2);
end

---String in decimal form to integer
---@param input string
---@return integer
function M.otoi(input)
return tonumber(input, 8);
end

return M
110 changes: 17 additions & 93 deletions lua/hexer/parser.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
local M = {}
local M = {
---@private
_utils = require("hexer.parse_utils")
}


---@class HexerChar
---@field ascii string
Expand All @@ -9,136 +13,56 @@ local M = {}

---@alias HexerItem HexerChar[]

-- Check whether a passed string is wrapped by quotes
---@param str string
---@return boolean
function M._is_string_wrapped(str)
return #str >= 3 and (
(str:sub(1, 1) == '"' and str:sub(str:len()) == '"') or
(str:sub(1, 1) == "'" and str:sub(str:len()) == "'")
)
end

---Parse an input given by the user into a HexerItem, last resorts to string
---@param input string
---@return HexerItem
function M.parse_input(input)
local value = tonumber(input)
if value ~= nil then
return { M._parse_from_int(value) }
return { M.parse_from_int(value) }
end

if #input == 1 then return { M._parse_from_int(input:byte(1)) } end
if #input == 1 then return { M.parse_from_int(input:byte(1)) } end

local head, tail = 1, input:len()
local orig_head = head


head = M._check_header(input, { 'x', 'X' })
if head ~= orig_head then return { M._parse_from_int(M._xtoi(input:sub(head))) } end
head = M._utils.check_header(input, { 'x', 'X' })
if head ~= orig_head then return { M.parse_from_int(M._utils.xtoi(input:sub(head))) } end

head = M._check_header(input, { 'b', 'B' })
if head ~= orig_head then return { M._parse_from_int(M._btoi(input:sub(head))) } end
head = M._utils.check_header(input, { 'b', 'B' })
if head ~= orig_head then return { M.parse_from_int(M._utils.btoi(input:sub(head))) } end

head = M._check_header(input, { 'o', 'O' })
if head ~= orig_head then return { M._parse_from_int(M._otoi(input:sub(head))) } end
head = M._utils.check_header(input, { 'o', 'O' })
if head ~= orig_head then return { M.parse_from_int(M._utils.otoi(input:sub(head))) } end

---@type HexerItem
local item = {}

if M._is_string_wrapped(input) then
if M._utils.is_string_wrapped(input) then
head = head + 1
tail = tail - 1
end

for i = head, tail do
item[i] = M._parse_from_int(input:byte(i))
item[i] = M.parse_from_int(input:byte(i))
end

return item
end

---String in decimal form to integer
---@param input string
---@return integer
function M._stoi(input)
return tonumber(input, 10);
end

---String in decimal form to integer
---@param input string
---@return integer
function M._xtoi(input)
return tonumber(input, 16);
end

---String in decimal form to integer
---@param input string
---@return integer
function M._btoi(input)
return tonumber(input, 2);
end

---String in decimal form to integer
---@param input string
---@return integer
function M._otoi(input)
return tonumber(input, 8);
end

---Integer in decimal form to binary string
---@param input integer
---@return string
function M._itob(input)
if input == 0 then
return "0"
end

if input < 0 then
input = math.abs(input)
end

local result = ""

while input > 0 do
local remainder = input % 2
result = remainder .. result
input = math.floor(input / 2)
end

return result
end

---Checks if the string has the given number format header and returns the start of the value if it does, otherwise 1
---@param str string
---@param header string[] the list of single-character format specifiers
---@return integer
function M._check_header(str, header)
for _, ch in ipairs(header) do
if str:sub(1, 1) == ch then
return 2
end

-- Should never occur, but just in case
if str:sub(1, 1) == '0' and str:sub(2, 2) == ch then
return 3
end
end

return 1
end

---Create a HexerChar from a given integer
---@param value integer
---@return HexerChar
function M._parse_from_int(value)
function M.parse_from_int(value)
---@type HexerChar
local item = {
value = value,
hex = "0x" .. string.format("%X", value),
octal = "0o" .. string.format("%o", value),
ascii = string.format("%c", value),
binary = "0b" .. M._itob(value),
binary = "0b" .. M._utils.itob(value),
}

return item
Expand Down
51 changes: 51 additions & 0 deletions lua/hexer/tests/parse_utils_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
local Utils = require("hexer.parse_utils")

describe("Parse Utils", function()
it("should be able to detect whether a passed string is quoted", function()
local strs = {
["''"] = false,
['""'] = false,
["potato"] = false,
["'p'"] = true,
['"p"'] = true,
["'potato'"] = true,
['"potato"'] = true
}

local res

for str, q in pairs(strs) do
res = Utils.is_string_wrapped(str) == q
assert(res, ("%s expected to be %s but got %s"):format(str, q, res))
end
end)

it("should see if a given string contains the header", function()
local strs = { "0b0011", "B0011" }
local headers = { { "b", "B" }, { "x", "X" } }

local val_pos = Utils.check_header(strs[1], headers[1])

assert(val_pos == 3,
("String %s with headers %s expected pos %s but got %s"):format(strs[1], vim.inspect(headers[1]), 3, val_pos)
)

val_pos = Utils.check_header(strs[1], headers[2])

assert(val_pos == 1,
("String %s with headers %s expected pos %s but got %s"):format(strs[1], vim.inspect(headers[2]), 1, val_pos)
)

val_pos = Utils.check_header(strs[2], headers[1])

assert(val_pos == 2,
("String %s with headers %s expected pos %s but got %s"):format(strs[2], vim.inspect(headers[1]), 2, val_pos)
)

val_pos = Utils.check_header(strs[2], headers[2])

assert(val_pos == 1,
("String %s with headers %s expected pos %s but got %s"):format(strs[2], vim.inspect(headers[2]), 1, val_pos)
)
end)
end)
Loading

0 comments on commit 4513e9c

Please sign in to comment.